gcs physical backend (#2099)
This commit is contained in:
parent
c29e5c8bad
commit
f07a19c503
|
@ -0,0 +1,205 @@
|
||||||
|
package physical
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/errwrap"
|
||||||
|
log "github.com/mgutz/logxi/v1"
|
||||||
|
|
||||||
|
"cloud.google.com/go/storage"
|
||||||
|
"github.com/armon/go-metrics"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GCSBackend is a physical backend that stores data
|
||||||
|
// within an Google Cloud Storage bucket.
|
||||||
|
type GCSBackend struct {
|
||||||
|
bucketName string
|
||||||
|
client *storage.Client
|
||||||
|
permitPool *PermitPool
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// newGCSBackend constructs a Google Cloud Storage backend using a pre-existing
|
||||||
|
// bucket. Credentials can be provided to the backend, sourced
|
||||||
|
// from environment variables or a service account file
|
||||||
|
func newGCSBackend(conf map[string]string, logger log.Logger) (Backend, error) {
|
||||||
|
|
||||||
|
bucketName := os.Getenv("GOOGLE_STORAGE_BUCKET")
|
||||||
|
|
||||||
|
if bucketName == "" {
|
||||||
|
bucketName = conf["bucket"]
|
||||||
|
if bucketName == "" {
|
||||||
|
return nil, fmt.Errorf("env var GOOGLE_STORAGE_BUCKET or configuration parameter 'bucket' must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// path to service account JSON file
|
||||||
|
credentialsFile := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
||||||
|
if credentialsFile == "" {
|
||||||
|
credentialsFile = conf["credentials_file"]
|
||||||
|
if credentialsFile == "" {
|
||||||
|
return nil, fmt.Errorf("env var GOOGLE_APPLICATION_CREDENTIALS or configuration parameter 'credentials_file' must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := storage.NewClient(
|
||||||
|
context.Background(),
|
||||||
|
option.WithServiceAccountFile(credentialsFile),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error establishing storage client: '%v'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check client connectivity by getting bucket attributes
|
||||||
|
_, err = client.Bucket(bucketName).Attrs(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to access bucket '%s': '%v'", bucketName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxParStr, ok := conf["max_parallel"]
|
||||||
|
var maxParInt int
|
||||||
|
if ok {
|
||||||
|
maxParInt, err = strconv.Atoi(maxParStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
|
||||||
|
}
|
||||||
|
if logger.IsDebug() {
|
||||||
|
logger.Debug("physical/gcs: max_parallel set", "max_parallel", maxParInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g := GCSBackend{
|
||||||
|
bucketName: bucketName,
|
||||||
|
client: client,
|
||||||
|
permitPool: NewPermitPool(maxParInt),
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put is used to insert or update an entry
|
||||||
|
func (g *GCSBackend) Put(entry *Entry) error {
|
||||||
|
defer metrics.MeasureSince([]string{"gcs", "put"}, time.Now())
|
||||||
|
|
||||||
|
bucket := g.client.Bucket(g.bucketName)
|
||||||
|
writer := bucket.Object(entry.Key).NewWriter(context.Background())
|
||||||
|
|
||||||
|
g.permitPool.Acquire()
|
||||||
|
defer g.permitPool.Release()
|
||||||
|
|
||||||
|
defer writer.Close()
|
||||||
|
_, err := writer.Write(entry.Value)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is used to fetch an entry
|
||||||
|
func (g *GCSBackend) Get(key string) (*Entry, error) {
|
||||||
|
defer metrics.MeasureSince([]string{"gcs", "get"}, time.Now())
|
||||||
|
|
||||||
|
bucket := g.client.Bucket(g.bucketName)
|
||||||
|
reader, err := bucket.Object(key).NewReader(context.Background())
|
||||||
|
|
||||||
|
// return (nil, nil) if object doesn't exist
|
||||||
|
if err == storage.ErrObjectNotExist {
|
||||||
|
return nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating bucket reader: '%v'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.permitPool.Acquire()
|
||||||
|
defer g.permitPool.Release()
|
||||||
|
|
||||||
|
defer reader.Close()
|
||||||
|
value, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading object '%v': '%v'", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ent := Entry{
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is used to permanently delete an entry
|
||||||
|
func (g *GCSBackend) Delete(key string) error {
|
||||||
|
defer metrics.MeasureSince([]string{"gcs", "delete"}, time.Now())
|
||||||
|
|
||||||
|
bucket := g.client.Bucket(g.bucketName)
|
||||||
|
|
||||||
|
g.permitPool.Acquire()
|
||||||
|
defer g.permitPool.Release()
|
||||||
|
|
||||||
|
err := bucket.Object(key).Delete(context.Background())
|
||||||
|
|
||||||
|
// deletion of non existent object is OK
|
||||||
|
if err == storage.ErrObjectNotExist {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("error deleting object '%v': '%v'", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is used to list all the keys under a given
|
||||||
|
// prefix, up to the next prefix.
|
||||||
|
func (g *GCSBackend) List(prefix string) ([]string, error) {
|
||||||
|
defer metrics.MeasureSince([]string{"gcs", "list"}, time.Now())
|
||||||
|
|
||||||
|
bucket := g.client.Bucket(g.bucketName)
|
||||||
|
|
||||||
|
objects_it := bucket.Objects(
|
||||||
|
context.Background(),
|
||||||
|
&storage.Query{
|
||||||
|
Prefix: prefix,
|
||||||
|
Delimiter: "/",
|
||||||
|
Versions: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
keys := []string{}
|
||||||
|
|
||||||
|
g.permitPool.Acquire()
|
||||||
|
defer g.permitPool.Release()
|
||||||
|
|
||||||
|
for {
|
||||||
|
objAttrs, err := objects_it.Next()
|
||||||
|
if err == iterator.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing bucket '%v': '%v'", g.bucketName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := ""
|
||||||
|
if objAttrs.Prefix != "" {
|
||||||
|
// "subdirectory"
|
||||||
|
path = objAttrs.Prefix
|
||||||
|
} else {
|
||||||
|
// file
|
||||||
|
path = objAttrs.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// get relative file/dir just like "basename"
|
||||||
|
key := strings.TrimPrefix(path, prefix)
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package physical
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/mgutz/logxi/v1"
|
||||||
|
|
||||||
|
"cloud.google.com/go/storage"
|
||||||
|
"github.com/hashicorp/vault/helper/logformat"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ConsistencyDelays = delays{
|
||||||
|
beforeList: 5 * time.Second,
|
||||||
|
beforeGet: 0 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGCSBackend(t *testing.T) {
|
||||||
|
credentialsFile := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
||||||
|
|
||||||
|
// projectID is only required for creating a bucket for this test
|
||||||
|
projectID := os.Getenv("GOOGLE_PROJECT_ID")
|
||||||
|
|
||||||
|
if credentialsFile == "" || projectID == "" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := storage.NewClient(
|
||||||
|
context.Background(),
|
||||||
|
option.WithServiceAccountFile(credentialsFile),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating storage client: '%v'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var randInt = rand.New(rand.NewSource(time.Now().UnixNano())).Int()
|
||||||
|
bucketName := fmt.Sprintf("vault-gcs-testacc-%d", randInt)
|
||||||
|
|
||||||
|
bucket := client.Bucket(bucketName)
|
||||||
|
err = bucket.Create(context.Background(), projectID, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating bucket '%v': '%v'", bucketName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test bucket teardown
|
||||||
|
defer func() {
|
||||||
|
objects_it := bucket.Objects(context.Background(), nil)
|
||||||
|
|
||||||
|
time.Sleep(ConsistencyDelays.beforeList)
|
||||||
|
// have to delete all objects before deleting bucket
|
||||||
|
for {
|
||||||
|
objAttrs, err := objects_it.Next()
|
||||||
|
if err == iterator.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error listing bucket '%v' contents: '%v'", bucketName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore errors in deleting a single object, we only care about deleting the bucket
|
||||||
|
// occassionally we get "storage: object doesn't exist" which is fine
|
||||||
|
bucket.Object(objAttrs.Name).Delete(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a list operation, but google lists to make sure the bucket is empty on delete
|
||||||
|
time.Sleep(ConsistencyDelays.beforeList)
|
||||||
|
err := bucket.Delete(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error deleting bucket '%s': '%v'", bucketName, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := logformat.NewVaultLogger(log.LevelTrace)
|
||||||
|
|
||||||
|
b, err := NewBackend("gcs", logger, map[string]string{
|
||||||
|
"bucket": bucketName,
|
||||||
|
"credentials_file": credentialsFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating google cloud storage backend: '%s'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testEventuallyConsistentBackend(t, b, ConsistencyDelays)
|
||||||
|
testEventuallyConsistentBackend_ListPrefix(t, b, ConsistencyDelays)
|
||||||
|
|
||||||
|
}
|
|
@ -128,6 +128,7 @@ var builtinBackends = map[string]Factory{
|
||||||
"mysql": newMySQLBackend,
|
"mysql": newMySQLBackend,
|
||||||
"postgresql": newPostgreSQLBackend,
|
"postgresql": newPostgreSQLBackend,
|
||||||
"swift": newSwiftBackend,
|
"swift": newSwiftBackend,
|
||||||
|
"gcs": newGCSBackend,
|
||||||
}
|
}
|
||||||
|
|
||||||
// PermitPool is used to limit maximum outstanding requests
|
// PermitPool is used to limit maximum outstanding requests
|
||||||
|
|
|
@ -356,3 +356,276 @@ func testHABackend(t *testing.T, b HABackend, b2 HABackend) {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
lock2.Unlock()
|
lock2.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type delays struct {
|
||||||
|
beforeGet time.Duration
|
||||||
|
beforeList time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEventuallyConsistentBackend(t *testing.T, b Backend, d delays) {
|
||||||
|
|
||||||
|
// no delay required: nothing written to bucket
|
||||||
|
// Should be empty
|
||||||
|
keys, err := b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should work if it does not exist
|
||||||
|
err = b.Delete("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// no delay required: nothing written to bucket
|
||||||
|
// Get should fail
|
||||||
|
out, err := b.Get("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if out != nil {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an entry
|
||||||
|
e := &Entry{Key: "foo", Value: []byte("test")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get should work
|
||||||
|
time.Sleep(d.beforeGet)
|
||||||
|
out, err = b.Get("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(out, e) {
|
||||||
|
t.Fatalf("bad: %v expected: %v", out, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List should not be empty
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 1 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
if keys[0] != "foo" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should work
|
||||||
|
err = b.Delete("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be empty
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get should fail
|
||||||
|
time.Sleep(d.beforeGet)
|
||||||
|
out, err = b.Get("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if out != nil {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple Puts should work; GH-189
|
||||||
|
e = &Entry{Key: "foo", Value: []byte("test")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
e = &Entry{Key: "foo", Value: []byte("test")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a nested entry
|
||||||
|
e = &Entry{Key: "foo/bar", Value: []byte("baz")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 2 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
if keys[0] != "foo" || keys[1] != "foo/" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete with children should work
|
||||||
|
err = b.Delete("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get should return the child
|
||||||
|
time.Sleep(d.beforeGet)
|
||||||
|
out, err = b.Get("foo/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if out == nil {
|
||||||
|
t.Fatalf("missing child")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removal of nested secret should not leave artifacts
|
||||||
|
e = &Entry{Key: "foo/nested1/nested2/nested3", Value: []byte("baz")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Delete("foo/nested1/nested2/nested3")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to remove nested secret: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("foo/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keys) != 1 {
|
||||||
|
t.Fatalf("there should be only one key left after deleting nested "+
|
||||||
|
"secret: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys[0] != "bar" {
|
||||||
|
t.Fatalf("bad keys after deleting nested: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a second nested entry to test prefix removal
|
||||||
|
e = &Entry{Key: "foo/zip", Value: []byte("zap")}
|
||||||
|
err = b.Put(e)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should not remove the prefix
|
||||||
|
err = b.Delete("foo/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 1 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
if keys[0] != "foo/" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should remove the prefix
|
||||||
|
err = b.Delete("foo/zip")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEventuallyConsistentBackend_ListPrefix(t *testing.T, b Backend, d delays) {
|
||||||
|
e1 := &Entry{Key: "foo", Value: []byte("test")}
|
||||||
|
e2 := &Entry{Key: "foo/bar", Value: []byte("test")}
|
||||||
|
e3 := &Entry{Key: "foo/bar/baz", Value: []byte("test")}
|
||||||
|
|
||||||
|
err := b.Put(e1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
err = b.Put(e2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
err = b.Put(e3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the root
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err := b.List("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 2 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
if keys[0] != "foo" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
if keys[1] != "foo/" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan foo/
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("foo/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 2 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
if keys[0] != "bar" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
if keys[1] != "bar/" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan foo/bar/
|
||||||
|
time.Sleep(d.beforeList)
|
||||||
|
keys, err = b.List("foo/bar/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
if len(keys) != 1 {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
if keys[0] != "baz" {
|
||||||
|
t.Fatalf("bad: %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014 Google Inc.
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,438 @@
|
||||||
|
// 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 metadata provides access to Google Compute Engine (GCE)
|
||||||
|
// metadata and API service accounts.
|
||||||
|
//
|
||||||
|
// This package is a wrapper around the GCE metadata service,
|
||||||
|
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||||
|
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/context/ctxhttp"
|
||||||
|
|
||||||
|
"cloud.google.com/go/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// metadataIP is the documented metadata server IP address.
|
||||||
|
metadataIP = "169.254.169.254"
|
||||||
|
|
||||||
|
// metadataHostEnv is the environment variable specifying the
|
||||||
|
// GCE metadata hostname. If empty, the default value of
|
||||||
|
// metadataIP ("169.254.169.254") is used instead.
|
||||||
|
// This is variable name is not defined by any spec, as far as
|
||||||
|
// I know; it was made up for the Go package.
|
||||||
|
metadataHostEnv = "GCE_METADATA_HOST"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cachedValue struct {
|
||||||
|
k string
|
||||||
|
trim bool
|
||||||
|
mu sync.Mutex
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||||
|
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||||
|
instID = &cachedValue{k: "instance/id", trim: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metaClient = &http.Client{
|
||||||
|
Transport: &internal.Transport{
|
||||||
|
Base: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
ResponseHeaderTimeout: 2 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subscribeClient = &http.Client{
|
||||||
|
Transport: &internal.Transport{
|
||||||
|
Base: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotDefinedError is returned when requested metadata is not defined.
|
||||||
|
//
|
||||||
|
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||||
|
//
|
||||||
|
// This error is not returned if the value is defined to be the empty
|
||||||
|
// string.
|
||||||
|
type NotDefinedError string
|
||||||
|
|
||||||
|
func (suffix NotDefinedError) Error() string {
|
||||||
|
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value from the metadata service.
|
||||||
|
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||||
|
//
|
||||||
|
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||||
|
// 169.254.169.254 will be used instead.
|
||||||
|
//
|
||||||
|
// If the requested metadata is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
func Get(suffix string) (string, error) {
|
||||||
|
val, _, err := getETag(metaClient, suffix)
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getETag returns a value from the metadata service as well as the associated
|
||||||
|
// ETag using the provided client. This func is otherwise equivalent to Get.
|
||||||
|
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
|
||||||
|
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||||
|
// a container, which is an important use-case for local testing of cloud
|
||||||
|
// deployments. To enable spoofing of the metadata service, the environment
|
||||||
|
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||||
|
// requests shall go.
|
||||||
|
host := os.Getenv(metadataHostEnv)
|
||||||
|
if host == "" {
|
||||||
|
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||||
|
// binaries built with the "netgo" tag and without cgo won't
|
||||||
|
// know the search suffix for "metadata" is
|
||||||
|
// ".google.internal", and this IP address is documented as
|
||||||
|
// being stable anyway.
|
||||||
|
host = metadataIP
|
||||||
|
}
|
||||||
|
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("Metadata-Flavor", "Google")
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return "", "", NotDefinedError(suffix)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||||
|
}
|
||||||
|
all, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return string(all), res.Header.Get("Etag"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrimmed(suffix string) (s string, err error) {
|
||||||
|
s, err = Get(suffix)
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedValue) get() (v string, err error) {
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.v != "" {
|
||||||
|
return c.v, nil
|
||||||
|
}
|
||||||
|
if c.trim {
|
||||||
|
v, err = getTrimmed(c.k)
|
||||||
|
} else {
|
||||||
|
v, err = Get(c.k)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
c.v = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
onGCEOnce sync.Once
|
||||||
|
onGCE bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||||
|
func OnGCE() bool {
|
||||||
|
onGCEOnce.Do(initOnGCE)
|
||||||
|
return onGCE
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOnGCE() {
|
||||||
|
onGCE = testOnGCE()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOnGCE() bool {
|
||||||
|
// The user explicitly said they're on GCE, so trust them.
|
||||||
|
if os.Getenv(metadataHostEnv) != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resc := make(chan bool, 2)
|
||||||
|
|
||||||
|
// Try two strategies in parallel.
|
||||||
|
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
|
||||||
|
go func() {
|
||||||
|
res, err := ctxhttp.Get(ctx, metaClient, "http://"+metadataIP)
|
||||||
|
if err != nil {
|
||||||
|
resc <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
addrs, err := net.LookupHost("metadata.google.internal")
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
resc <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resc <- strsContains(addrs, metadataIP)
|
||||||
|
}()
|
||||||
|
|
||||||
|
tryHarder := systemInfoSuggestsGCE()
|
||||||
|
if tryHarder {
|
||||||
|
res := <-resc
|
||||||
|
if res {
|
||||||
|
// The first strategy succeeded, so let's use it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Wait for either the DNS or metadata server probe to
|
||||||
|
// contradict the other one and say we are running on
|
||||||
|
// GCE. Give it a lot of time to do so, since the system
|
||||||
|
// info already suggests we're running on a GCE BIOS.
|
||||||
|
timer := time.NewTimer(5 * time.Second)
|
||||||
|
defer timer.Stop()
|
||||||
|
select {
|
||||||
|
case res = <-resc:
|
||||||
|
return res
|
||||||
|
case <-timer.C:
|
||||||
|
// Too slow. Who knows what this system is.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no hint from the system info that we're running on
|
||||||
|
// GCE, so use the first probe's result as truth, whether it's
|
||||||
|
// true or false. The goal here is to optimize for speed for
|
||||||
|
// users who are NOT running on GCE. We can't assume that
|
||||||
|
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||||
|
// address is fast. Worst case this should return when the
|
||||||
|
// metaClient's Transport.ResponseHeaderTimeout or
|
||||||
|
// Transport.Dial.Timeout fires (in two seconds).
|
||||||
|
return <-resc
|
||||||
|
}
|
||||||
|
|
||||||
|
// systemInfoSuggestsGCE reports whether the local system (without
|
||||||
|
// doing network requests) suggests that we're running on GCE. If this
|
||||||
|
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||||
|
// server.
|
||||||
|
func systemInfoSuggestsGCE() bool {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
// We don't have any non-Linux clues available, at least yet.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||||
|
name := strings.TrimSpace(string(slurp))
|
||||||
|
return name == "Google" || name == "Google Compute Engine"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes to a value from the metadata service.
|
||||||
|
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||||
|
// The suffix may contain query parameters.
|
||||||
|
//
|
||||||
|
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||||
|
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||||
|
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||||
|
// is deleted. Subscribe returns the error value returned from the last call to
|
||||||
|
// fn, which may be nil when ok == false.
|
||||||
|
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||||
|
const failedSubscribeSleep = time.Second * 5
|
||||||
|
|
||||||
|
// First check to see if the metadata value exists at all.
|
||||||
|
val, lastETag, err := getETag(subscribeClient, suffix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(val, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
if strings.ContainsRune(suffix, '?') {
|
||||||
|
suffix += "&wait_for_change=true&last_etag="
|
||||||
|
} else {
|
||||||
|
suffix += "?wait_for_change=true&last_etag="
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
|
||||||
|
if err != nil {
|
||||||
|
if _, deleted := err.(NotDefinedError); !deleted {
|
||||||
|
time.Sleep(failedSubscribeSleep)
|
||||||
|
continue // Retry on other errors.
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
lastETag = etag
|
||||||
|
|
||||||
|
if err := fn(val, ok); err != nil || !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectID returns the current instance's project ID string.
|
||||||
|
func ProjectID() (string, error) { return projID.get() }
|
||||||
|
|
||||||
|
// NumericProjectID returns the current instance's numeric project ID.
|
||||||
|
func NumericProjectID() (string, error) { return projNum.get() }
|
||||||
|
|
||||||
|
// InternalIP returns the instance's primary internal IP address.
|
||||||
|
func InternalIP() (string, error) {
|
||||||
|
return getTrimmed("instance/network-interfaces/0/ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalIP returns the instance's primary external (public) IP address.
|
||||||
|
func ExternalIP() (string, error) {
|
||||||
|
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostname returns the instance's hostname. This will be of the form
|
||||||
|
// "<instanceID>.c.<projID>.internal".
|
||||||
|
func Hostname() (string, error) {
|
||||||
|
return getTrimmed("instance/hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceTags returns the list of user-defined instance tags,
|
||||||
|
// assigned when initially creating a GCE instance.
|
||||||
|
func InstanceTags() ([]string, error) {
|
||||||
|
var s []string
|
||||||
|
j, err := Get("instance/tags")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceID returns the current VM's numeric instance ID.
|
||||||
|
func InstanceID() (string, error) {
|
||||||
|
return instID.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceName returns the current VM's instance ID string.
|
||||||
|
func InstanceName() (string, error) {
|
||||||
|
host, err := Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.Split(host, ".")[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||||
|
func Zone() (string, error) {
|
||||||
|
zone, err := getTrimmed("instance/zone")
|
||||||
|
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceAttributes returns the list of user-defined attributes,
|
||||||
|
// assigned when initially creating a GCE VM instance. The value of an
|
||||||
|
// attribute can be obtained with InstanceAttributeValue.
|
||||||
|
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
||||||
|
|
||||||
|
// ProjectAttributes returns the list of user-defined attributes
|
||||||
|
// applying to the project as a whole, not just this VM. The value of
|
||||||
|
// an attribute can be obtained with ProjectAttributeValue.
|
||||||
|
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||||
|
|
||||||
|
func lines(suffix string) ([]string, error) {
|
||||||
|
j, err := Get(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||||
|
for i := range s {
|
||||||
|
s[i] = strings.TrimSpace(s[i])
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceAttributeValue returns the value of the provided VM
|
||||||
|
// instance attribute.
|
||||||
|
//
|
||||||
|
// If the requested attribute is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
//
|
||||||
|
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||||
|
// defined to be the empty string.
|
||||||
|
func InstanceAttributeValue(attr string) (string, error) {
|
||||||
|
return Get("instance/attributes/" + attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectAttributeValue returns the value of the provided
|
||||||
|
// project attribute.
|
||||||
|
//
|
||||||
|
// If the requested attribute is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
//
|
||||||
|
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||||
|
// defined to be the empty string.
|
||||||
|
func ProjectAttributeValue(attr string) (string, error) {
|
||||||
|
return Get("project/attributes/" + attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes returns the service account scopes for the given account.
|
||||||
|
// The account may be empty or the string "default" to use the instance's
|
||||||
|
// main account.
|
||||||
|
func Scopes(serviceAccount string) ([]string, error) {
|
||||||
|
if serviceAccount == "" {
|
||||||
|
serviceAccount = "default"
|
||||||
|
}
|
||||||
|
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func strsContains(ss []string, s string) bool {
|
||||||
|
for _, v := range ss {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 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 internal provides support for the cloud packages.
|
||||||
|
//
|
||||||
|
// Users should not import this package directly.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const userAgent = "gcloud-golang/0.1"
|
||||||
|
|
||||||
|
// Transport is an http.RoundTripper that appends Google Cloud client's
|
||||||
|
// user-agent to the original request's user-agent header.
|
||||||
|
type Transport struct {
|
||||||
|
// TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does.
|
||||||
|
// Do User-Agent some other way.
|
||||||
|
|
||||||
|
// Base is the actual http.RoundTripper
|
||||||
|
// requests will use. It must not be nil.
|
||||||
|
Base http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip appends a user-agent to the existing user-agent
|
||||||
|
// header and delegates the request to the base http.RoundTripper.
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req = cloneRequest(req)
|
||||||
|
ua := req.Header.Get("User-Agent")
|
||||||
|
if ua == "" {
|
||||||
|
ua = userAgent
|
||||||
|
} else {
|
||||||
|
ua = fmt.Sprintf("%s %s", ua, userAgent)
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", ua)
|
||||||
|
return t.Base.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func cloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header)
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = s
|
||||||
|
}
|
||||||
|
return r2
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright 2016 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 optional provides versions of primitive types that can
|
||||||
|
// be nil. These are useful in methods that update some of an API object's
|
||||||
|
// fields.
|
||||||
|
package optional
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Bool is either a bool or nil.
|
||||||
|
Bool interface{}
|
||||||
|
|
||||||
|
// String is either a string or nil.
|
||||||
|
String interface{}
|
||||||
|
|
||||||
|
// Int is either an int or nil.
|
||||||
|
Int interface{}
|
||||||
|
|
||||||
|
// Uint is either a uint or nil.
|
||||||
|
Uint interface{}
|
||||||
|
|
||||||
|
// Float64 is either a float64 or nil.
|
||||||
|
Float64 interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToBool returns its argument as a bool.
|
||||||
|
// It panics if its argument is nil or not a bool.
|
||||||
|
func ToBool(v Bool) bool {
|
||||||
|
x, ok := v.(bool)
|
||||||
|
if !ok {
|
||||||
|
doPanic("Bool", v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns its argument as a string.
|
||||||
|
// It panics if its argument is nil or not a string.
|
||||||
|
func ToString(v String) string {
|
||||||
|
x, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
doPanic("String", v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt returns its argument as an int.
|
||||||
|
// It panics if its argument is nil or not an int.
|
||||||
|
func ToInt(v Int) int {
|
||||||
|
x, ok := v.(int)
|
||||||
|
if !ok {
|
||||||
|
doPanic("Int", v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint returns its argument as a uint.
|
||||||
|
// It panics if its argument is nil or not a uint.
|
||||||
|
func ToUint(v Uint) uint {
|
||||||
|
x, ok := v.(uint)
|
||||||
|
if !ok {
|
||||||
|
doPanic("Uint", v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 returns its argument as a float64.
|
||||||
|
// It panics if its argument is nil or not a float64.
|
||||||
|
func ToFloat64(v Float64) float64 {
|
||||||
|
x, ok := v.(float64)
|
||||||
|
if !ok {
|
||||||
|
doPanic("Float64", v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPanic(capType string, v interface{}) {
|
||||||
|
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
// 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 level of access to grant.
|
||||||
|
type ACLRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleOwner ACLRole = "OWNER"
|
||||||
|
RoleReader ACLRole = "READER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACLEntity refers to a user or group.
|
||||||
|
// They are sometimes referred to as grantees.
|
||||||
|
//
|
||||||
|
// 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 a grant for a role to an entity (user, group or team) for a Google Cloud Storage object or bucket.
|
||||||
|
type ACLRule struct {
|
||||||
|
Entity ACLEntity
|
||||||
|
Role ACLRole
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete permanently deletes the ACL entry for the given entity.
|
||||||
|
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) error {
|
||||||
|
if a.object != "" {
|
||||||
|
return a.objectDelete(ctx, entity)
|
||||||
|
}
|
||||||
|
if a.isDefault {
|
||||||
|
return a.bucketDefaultDelete(ctx, entity)
|
||||||
|
}
|
||||||
|
return a.bucketDelete(ctx, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
if a.isDefault {
|
||||||
|
return a.bucketDefaultSet(ctx, entity, role)
|
||||||
|
}
|
||||||
|
return a.bucketSet(ctx, entity, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List retrieves ACL entries.
|
||||||
|
func (a *ACLHandle) List(ctx context.Context) ([]ACLRule, error) {
|
||||||
|
if a.object != "" {
|
||||||
|
return a.objectList(ctx)
|
||||||
|
}
|
||||||
|
if a.isDefault {
|
||||||
|
return a.bucketDefaultList(ctx)
|
||||||
|
}
|
||||||
|
return a.bucketList(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
|
||||||
|
var acls *raw.ObjectAccessControls
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error {
|
||||||
|
acls, err = a.c.raw.DefaultObjectAccessControls.List(a.bucket).Context(ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("storage: error listing default object ACL for bucket %q: %v", a.bucket, err)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
_, err := a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl).Context(ctx).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 {
|
||||||
|
return a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity)).Context(ctx).Do()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("storage: error deleting default ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
||||||
|
var acls *raw.BucketAccessControls
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error {
|
||||||
|
acls, err = a.c.raw.BucketAccessControls.List(a.bucket).Context(ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("storage: error listing bucket ACL for bucket %q: %v", a.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||||
|
acl := &raw.BucketAccessControl{
|
||||||
|
Bucket: a.bucket,
|
||||||
|
Entity: string(entity),
|
||||||
|
Role: string(role),
|
||||||
|
}
|
||||||
|
err := runWithRetry(ctx, func() error {
|
||||||
|
_, err := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl).Context(ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("storage: error updating bucket ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||||
|
err := runWithRetry(ctx, func() error {
|
||||||
|
return a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity)).Context(ctx).Do()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("storage: error deleting bucket ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
||||||
|
var acls *raw.ObjectAccessControls
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error {
|
||||||
|
acls, err = a.c.raw.ObjectAccessControls.List(a.bucket, a.object).Context(ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("storage: error listing object ACL for bucket %q, file %q: %v", a.bucket, a.object, err)
|
||||||
|
}
|
||||||
|
return toACLRules(acls.Items), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) objectSet(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 {
|
||||||
|
_, err := a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl).Context(ctx).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)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
|
||||||
|
err := runWithRetry(ctx, func() error {
|
||||||
|
return a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity)).Context(ctx).Do()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("storage: error deleting object ACL entry for bucket %q, file %q, entity %q: %v", a.bucket, a.object, entity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||||
|
r := make([]ACLRule, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
r = append(r, ACLRule{Entity: ACLEntity(item.Entity), Role: ACLRole(item.Role)})
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,328 @@
|
||||||
|
// 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 (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
raw "google.golang.org/api/storage/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create creates the Bucket in the project.
|
||||||
|
// If attrs is nil the API defaults will be used.
|
||||||
|
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) error {
|
||||||
|
var bkt *raw.Bucket
|
||||||
|
if attrs != nil {
|
||||||
|
bkt = attrs.toRawBucket()
|
||||||
|
} else {
|
||||||
|
bkt = &raw.Bucket{}
|
||||||
|
}
|
||||||
|
bkt.Name = b.name
|
||||||
|
req := b.c.raw.Buckets.Insert(projectID, bkt)
|
||||||
|
return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the Bucket.
|
||||||
|
func (b *BucketHandle) Delete(ctx context.Context) error {
|
||||||
|
req := b.c.raw.Buckets.Delete(b.name)
|
||||||
|
return runWithRetry(ctx, func() error { return req.Context(ctx).Do() })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACL returns an ACLHandle, which provides access to the bucket's access control list.
|
||||||
|
// This controls who can list, create or overwrite the objects in a bucket.
|
||||||
|
// This call does not perform any network operations.
|
||||||
|
func (b *BucketHandle) ACL() *ACLHandle {
|
||||||
|
return &b.acl
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultObjectACL returns an ACLHandle, which provides access to the bucket's default object ACLs.
|
||||||
|
// These ACLs are applied to newly created objects in this bucket that do not have a defined ACL.
|
||||||
|
// This call does not perform any network operations.
|
||||||
|
func (b *BucketHandle) DefaultObjectACL() *ACLHandle {
|
||||||
|
return &b.defaultObjectACL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object returns an ObjectHandle, which provides operations on the named object.
|
||||||
|
// This call does not perform any network operations.
|
||||||
|
//
|
||||||
|
// name must consist entirely of valid UTF-8-encoded runes. The full specification
|
||||||
|
// for valid object names can be found at:
|
||||||
|
// https://cloud.google.com/storage/docs/bucket-naming
|
||||||
|
func (b *BucketHandle) Object(name string) *ObjectHandle {
|
||||||
|
return &ObjectHandle{
|
||||||
|
c: b.c,
|
||||||
|
bucket: b.name,
|
||||||
|
object: name,
|
||||||
|
acl: ACLHandle{
|
||||||
|
c: b.c,
|
||||||
|
bucket: b.name,
|
||||||
|
object: name,
|
||||||
|
},
|
||||||
|
gen: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attrs returns the metadata for the bucket.
|
||||||
|
func (b *BucketHandle) Attrs(ctx context.Context) (*BucketAttrs, error) {
|
||||||
|
var resp *raw.Bucket
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error {
|
||||||
|
resp, err = b.c.raw.Buckets.Get(b.name).Projection("full").Context(ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||||
|
return nil, ErrBucketNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newBucket(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BucketAttrs represents the metadata for a Google Cloud Storage bucket.
|
||||||
|
type BucketAttrs 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.
|
||||||
|
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 "MULTI_REGIONAL",
|
||||||
|
// "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" and
|
||||||
|
// "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD", which
|
||||||
|
// is equivalent to "MULTI_REGIONAL" or "REGIONAL" depending on
|
||||||
|
// the bucket's location settings.
|
||||||
|
StorageClass string
|
||||||
|
|
||||||
|
// Created is the creation time of the bucket.
|
||||||
|
Created time.Time
|
||||||
|
|
||||||
|
// VersioningEnabled reports whether this bucket has versioning enabled.
|
||||||
|
// This field is read-only.
|
||||||
|
VersioningEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBucket(b *raw.Bucket) *BucketAttrs {
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bucket := &BucketAttrs{
|
||||||
|
Name: b.Name,
|
||||||
|
Location: b.Location,
|
||||||
|
MetaGeneration: b.Metageneration,
|
||||||
|
StorageClass: b.StorageClass,
|
||||||
|
Created: convertTime(b.TimeCreated),
|
||||||
|
VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// toRawBucket copies the editable attribute from b to the raw library's Bucket type.
|
||||||
|
func (b *BucketAttrs) toRawBucket() *raw.Bucket {
|
||||||
|
var acl []*raw.BucketAccessControl
|
||||||
|
if len(b.ACL) > 0 {
|
||||||
|
acl = make([]*raw.BucketAccessControl, len(b.ACL))
|
||||||
|
for i, rule := range b.ACL {
|
||||||
|
acl[i] = &raw.BucketAccessControl{
|
||||||
|
Entity: string(rule.Entity),
|
||||||
|
Role: string(rule.Role),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dACL := toRawObjectACL(b.DefaultObjectACL)
|
||||||
|
return &raw.Bucket{
|
||||||
|
Name: b.Name,
|
||||||
|
DefaultObjectAcl: dACL,
|
||||||
|
Location: b.Location,
|
||||||
|
StorageClass: b.StorageClass,
|
||||||
|
Acl: acl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
it := &ObjectIterator{
|
||||||
|
ctx: ctx,
|
||||||
|
bucket: b,
|
||||||
|
}
|
||||||
|
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||||
|
it.fetch,
|
||||||
|
func() int { return len(it.items) },
|
||||||
|
func() interface{} { b := it.items; it.items = nil; return b })
|
||||||
|
if q != nil {
|
||||||
|
it.query = *q
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ObjectIterator is an iterator over ObjectAttrs.
|
||||||
|
type ObjectIterator struct {
|
||||||
|
ctx context.Context
|
||||||
|
bucket *BucketHandle
|
||||||
|
query Query
|
||||||
|
pageInfo *iterator.PageInfo
|
||||||
|
nextFunc func() error
|
||||||
|
items []*ObjectAttrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||||
|
func (it *ObjectIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||||
|
|
||||||
|
// Next returns the next result. Its second return value is iterator.Done if
|
||||||
|
// there are no more results. Once Next returns iterator.Done, all subsequent
|
||||||
|
// calls will return iterator.Done.
|
||||||
|
//
|
||||||
|
// If Query.Delimiter is non-empty, some of the ObjectAttrs returned by Next will
|
||||||
|
// have a non-empty Prefix field, and a zero value for all other fields. These
|
||||||
|
// represent prefixes.
|
||||||
|
func (it *ObjectIterator) Next() (*ObjectAttrs, error) {
|
||||||
|
if err := it.nextFunc(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item := it.items[0]
|
||||||
|
it.items = it.items[1:]
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||||
|
req := it.bucket.c.raw.Objects.List(it.bucket.name)
|
||||||
|
req.Projection("full")
|
||||||
|
req.Delimiter(it.query.Delimiter)
|
||||||
|
req.Prefix(it.query.Prefix)
|
||||||
|
req.Versions(it.query.Versions)
|
||||||
|
req.PageToken(pageToken)
|
||||||
|
if pageSize > 0 {
|
||||||
|
req.MaxResults(int64(pageSize))
|
||||||
|
}
|
||||||
|
var resp *raw.Objects
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(it.ctx, func() error {
|
||||||
|
resp, err = req.Context(it.ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, item := range resp.Items {
|
||||||
|
it.items = append(it.items, newObject(item))
|
||||||
|
}
|
||||||
|
for _, prefix := range resp.Prefixes {
|
||||||
|
it.items = append(it.items, &ObjectAttrs{Prefix: prefix})
|
||||||
|
}
|
||||||
|
return resp.NextPageToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jbd): Add storage.buckets.update.
|
||||||
|
|
||||||
|
// Buckets returns an iterator over the buckets in the project. You may
|
||||||
|
// optionally set the iterator's Prefix field to restrict the list to buckets
|
||||||
|
// whose names begin with the prefix. By default, all buckets in the project
|
||||||
|
// are returned.
|
||||||
|
func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator {
|
||||||
|
it := &BucketIterator{
|
||||||
|
ctx: ctx,
|
||||||
|
client: c,
|
||||||
|
projectID: projectID,
|
||||||
|
}
|
||||||
|
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||||
|
it.fetch,
|
||||||
|
func() int { return len(it.buckets) },
|
||||||
|
func() interface{} { b := it.buckets; it.buckets = nil; return b })
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// A BucketIterator is an iterator over BucketAttrs.
|
||||||
|
type BucketIterator struct {
|
||||||
|
// Prefix restricts the iterator to buckets whose names begin with it.
|
||||||
|
Prefix string
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
client *Client
|
||||||
|
projectID string
|
||||||
|
buckets []*BucketAttrs
|
||||||
|
pageInfo *iterator.PageInfo
|
||||||
|
nextFunc func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next result. Its second return value is iterator.Done if
|
||||||
|
// there are no more results. Once Next returns iterator.Done, all subsequent
|
||||||
|
// calls will return iterator.Done.
|
||||||
|
func (it *BucketIterator) Next() (*BucketAttrs, error) {
|
||||||
|
if err := it.nextFunc(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := it.buckets[0]
|
||||||
|
it.buckets = it.buckets[1:]
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||||
|
func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||||
|
|
||||||
|
func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||||
|
req := it.client.raw.Buckets.List(it.projectID)
|
||||||
|
req.Projection("full")
|
||||||
|
req.Prefix(it.Prefix)
|
||||||
|
req.PageToken(pageToken)
|
||||||
|
if pageSize > 0 {
|
||||||
|
req.MaxResults(int64(pageSize))
|
||||||
|
}
|
||||||
|
var resp *raw.Buckets
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(it.ctx, func() error {
|
||||||
|
resp, err = req.Context(it.ctx).Do()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, item := range resp.Items {
|
||||||
|
it.buckets = append(it.buckets, newBucket(item))
|
||||||
|
}
|
||||||
|
return resp.NextPageToken, nil
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
// Copyright 2016 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 (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
raw "google.golang.org/api/storage/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
|
||||||
|
return &Copier{dst: dst, src: src}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Copier copies a source object to a destination.
|
||||||
|
type Copier struct {
|
||||||
|
// ObjectAttrs are optional attributes to set on the destination object.
|
||||||
|
// Any attributes must be initialized before any calls on the Copier. Nil
|
||||||
|
// or zero-valued attributes are ignored.
|
||||||
|
ObjectAttrs
|
||||||
|
|
||||||
|
// RewriteToken can be set before calling Run to resume a copy
|
||||||
|
// operation. After Run returns a non-nil error, RewriteToken will
|
||||||
|
// have been updated to contain the value needed to resume the copy.
|
||||||
|
RewriteToken string
|
||||||
|
|
||||||
|
// ProgressFunc can be used to monitor the progress of a multi-RPC copy
|
||||||
|
// operation. If ProgressFunc is not nil and CopyFrom requires multiple
|
||||||
|
// calls to the underlying service (see
|
||||||
|
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
|
||||||
|
// ProgressFunc will be invoked after each call with the number of bytes of
|
||||||
|
// content copied so far and the total size in bytes of the source object.
|
||||||
|
//
|
||||||
|
// ProgressFunc is intended to make upload progress available to the
|
||||||
|
// application. For example, the implementation of ProgressFunc may update
|
||||||
|
// a progress bar in the application's UI, or log the result of
|
||||||
|
// float64(copiedBytes)/float64(totalBytes).
|
||||||
|
//
|
||||||
|
// ProgressFunc should return quickly without blocking.
|
||||||
|
ProgressFunc func(copiedBytes, totalBytes uint64)
|
||||||
|
|
||||||
|
dst, src *ObjectHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the copy.
|
||||||
|
func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
|
||||||
|
if err := c.src.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.dst.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var rawObject *raw.Object
|
||||||
|
// If any attribute was set, then we make sure the name matches the destination
|
||||||
|
// name, and we check that ContentType is non-empty so we can provide a better
|
||||||
|
// error message than the service.
|
||||||
|
if !reflect.DeepEqual(c.ObjectAttrs, ObjectAttrs{}) {
|
||||||
|
c.ObjectAttrs.Name = c.dst.object
|
||||||
|
if c.ObjectAttrs.ContentType == "" {
|
||||||
|
return nil, errors.New("storage: Copier.ContentType must be non-empty")
|
||||||
|
}
|
||||||
|
rawObject = c.ObjectAttrs.toRawObject(c.dst.bucket)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
res, err := c.callRewrite(ctx, c.src, rawObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c.ProgressFunc != nil {
|
||||||
|
c.ProgressFunc(res.TotalBytesRewritten, res.ObjectSize)
|
||||||
|
}
|
||||||
|
if res.Done { // Finished successfully.
|
||||||
|
return newObject(res.Resource), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
call.Context(ctx).Projection("full")
|
||||||
|
if c.RewriteToken != "" {
|
||||||
|
call.RewriteToken(c.RewriteToken)
|
||||||
|
}
|
||||||
|
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res *raw.RewriteResponse
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.RewriteToken = res.RewriteToken
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposerFrom creates a Composer that can compose srcs into dst.
|
||||||
|
// You can immediately call Run on the returned Composer, or you can
|
||||||
|
// configure it first.
|
||||||
|
//
|
||||||
|
// The encryption key for the destination object will be used to decrypt all
|
||||||
|
// source objects and encrypt the destination object. It is an error
|
||||||
|
// to specify an encryption key for any of the source objects.
|
||||||
|
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
|
||||||
|
return &Composer{dst: dst, srcs: srcs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Composer composes source objects into a destination object.
|
||||||
|
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
|
||||||
|
// or zero-valued attributes are ignored.
|
||||||
|
ObjectAttrs
|
||||||
|
|
||||||
|
dst *ObjectHandle
|
||||||
|
srcs []*ObjectHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the compose operation.
|
||||||
|
func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
|
||||||
|
if err := c.dst.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(c.srcs) == 0 {
|
||||||
|
return nil, errors.New("storage: at least one source object must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &raw.ComposeRequest{}
|
||||||
|
// Compose requires a non-empty Destination, so we always set it,
|
||||||
|
// even if the caller-provided ObjectAttrs is the zero value.
|
||||||
|
req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
|
||||||
|
for _, src := range c.srcs {
|
||||||
|
if err := src.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if src.bucket != c.dst.bucket {
|
||||||
|
return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
|
||||||
|
}
|
||||||
|
if src.encryptionKey != nil {
|
||||||
|
return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
|
||||||
|
}
|
||||||
|
srcObj := &raw.ComposeRequestSourceObjects{
|
||||||
|
Name: src.object,
|
||||||
|
}
|
||||||
|
if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.SourceObjects = append(req.SourceObjects, srcObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
|
||||||
|
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var obj *raw.Object
|
||||||
|
var err error
|
||||||
|
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newObject(obj), nil
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
// Copyright 2016 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 provides an easy way to work with Google Cloud Storage.
|
||||||
|
Google Cloud Storage stores data in named objects, which are grouped into buckets.
|
||||||
|
|
||||||
|
More information about Google Cloud Storage is available at
|
||||||
|
https://cloud.google.com/storage/docs.
|
||||||
|
|
||||||
|
All of the methods of this package use exponential backoff to retry calls
|
||||||
|
that fail with certain errors, as described in
|
||||||
|
https://cloud.google.com/storage/docs/exponential-backoff.
|
||||||
|
|
||||||
|
Note: This package is experimental and may make backwards-incompatible changes.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a Client
|
||||||
|
|
||||||
|
To start working with this package, create a client:
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
client, err := storage.NewClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
Buckets
|
||||||
|
|
||||||
|
A Google Cloud Storage bucket is a collection of objects. To work with a
|
||||||
|
bucket, make a bucket handle:
|
||||||
|
|
||||||
|
bkt := client.Bucket(bucketName)
|
||||||
|
|
||||||
|
A handle is a reference to a bucket. You can have a handle even if the
|
||||||
|
bucket doesn't exist yet. To create a bucket in Google Cloud Storage,
|
||||||
|
call Create on the handle:
|
||||||
|
|
||||||
|
if err := bkt.Create(ctx, projectID, nil); err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that although buckets are associated with projects, bucket names are
|
||||||
|
global across all projects.
|
||||||
|
|
||||||
|
Each bucket has associated metadata, represented in this package by
|
||||||
|
BucketAttrs. The third argument to BucketHandle.Create allows you to set
|
||||||
|
the intial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
|
||||||
|
Attrs:
|
||||||
|
|
||||||
|
attrs, err := bkt.Attrs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
fmt.Printf("bucket %s, created at %s, is located in %s with storage class %s\n",
|
||||||
|
attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)
|
||||||
|
|
||||||
|
Objects
|
||||||
|
|
||||||
|
An object holds arbitrary data as a sequence of bytes, like a file. You
|
||||||
|
refer to objects using a handle, just as with buckets. You can use the
|
||||||
|
standard Go io.Reader and io.Writer interfaces to read and write
|
||||||
|
object data:
|
||||||
|
|
||||||
|
obj := bkt.Object("data")
|
||||||
|
// Write something to obj.
|
||||||
|
// w implements io.Writer.
|
||||||
|
w := obj.NewWriter(ctx)
|
||||||
|
// Write some text to obj. This will overwrite whatever is there.
|
||||||
|
if _, err := fmt.Fprintf(w, "This object contains text.\n"); err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
// Close, just like writing a file.
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read it back.
|
||||||
|
r, err := obj.NewReader(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
if _, err := io.Copy(os.Stdout, r); err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
// Prints "This object contains text."
|
||||||
|
|
||||||
|
Objects also have attributes, which you can fetch with Attrs:
|
||||||
|
|
||||||
|
objAttrs, err := obj.Attrs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
fmt.Printf("object %s has size %d and can be read using %s\n",
|
||||||
|
objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)
|
||||||
|
|
||||||
|
ACLs
|
||||||
|
|
||||||
|
Both objects and buckets have ACLs (Access Control Lists). An ACL is a list of
|
||||||
|
ACLRules, each of which specifies the role of a user, group or project. ACLs
|
||||||
|
are suitable for fine-grained control, but you may prefer using IAM to control
|
||||||
|
access at the project level (see
|
||||||
|
https://cloud.google.com/storage/docs/access-control/iam).
|
||||||
|
|
||||||
|
To list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
|
||||||
|
|
||||||
|
acls, err := obj.ACL().List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
for _, rule := range acls {
|
||||||
|
fmt.Printf("%s has role %s\n", rule.Entity, rule.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also set and delete ACLs.
|
||||||
|
|
||||||
|
Conditions
|
||||||
|
|
||||||
|
Every object has a generation and a metageneration. The generation changes
|
||||||
|
whenever the content changes, and the metageneration changes whenever the
|
||||||
|
metadata changes. Conditions let you check these values before an operation;
|
||||||
|
the operation only executes if the conditions match. You can use conditions to
|
||||||
|
prevent race conditions in read-modify-write operations.
|
||||||
|
|
||||||
|
For example, say you've read an object's metadata into objAttrs. Now
|
||||||
|
you want to write to that object, but only if its contents haven't changed
|
||||||
|
since you read it. Here is how to express that:
|
||||||
|
|
||||||
|
w = obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)
|
||||||
|
// Proceed with writing as above.
|
||||||
|
|
||||||
|
Signed URLs
|
||||||
|
|
||||||
|
You can obtain a URL that lets anyone read or write an object for a limited time.
|
||||||
|
You don't need to create a client to do this. See the documentation of
|
||||||
|
SignedURL for details.
|
||||||
|
|
||||||
|
url, err := storage.SignedURL(bucketName, "shared-object", opts)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Handle error.
|
||||||
|
}
|
||||||
|
fmt.Println(url)
|
||||||
|
*/
|
||||||
|
package storage // import "cloud.google.com/go/storage"
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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 (
|
||||||
|
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
|
||||||
|
// the context is done.
|
||||||
|
func runWithRetry(ctx context.Context, call func() error) error {
|
||||||
|
var backoff gax.Backoff // use defaults for gax exponential backoff
|
||||||
|
for {
|
||||||
|
err := call()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e, ok := err.(*googleapi.Error)
|
||||||
|
if !ok {
|
||||||
|
return 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 err := gax.Sleep(ctx, backoff.Pause()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2016 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 (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader reads a Cloud Storage object.
|
||||||
|
// It implements io.Reader.
|
||||||
|
type Reader struct {
|
||||||
|
body io.ReadCloser
|
||||||
|
remain, size int64
|
||||||
|
contentType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the Reader. It must be called when done reading.
|
||||||
|
func (r *Reader) Close() error {
|
||||||
|
return r.body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (int, error) {
|
||||||
|
n, err := r.body.Read(p)
|
||||||
|
if r.remain != -1 {
|
||||||
|
r.remain -= int64(n)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the object in bytes.
|
||||||
|
// The returned value is always the same and is not affected by
|
||||||
|
// calls to Read or Close.
|
||||||
|
func (r *Reader) Size() int64 {
|
||||||
|
return r.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remain returns the number of bytes left to read, or -1 if unknown.
|
||||||
|
func (r *Reader) Remain() int64 {
|
||||||
|
return r.remain
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns the content type of the object.
|
||||||
|
func (r *Reader) ContentType() string {
|
||||||
|
return r.contentType
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
raw "google.golang.org/api/storage/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// ChunkSize controls the maximum number of bytes of the object that the
|
||||||
|
// Writer will attempt to send to the server in a single request. Objects
|
||||||
|
// smaller than the size will be sent in a single request, while larger
|
||||||
|
// objects will be split over multiple requests. The size will be rounded up
|
||||||
|
// to the nearest multiple of 256K. If zero, chunking will be disabled and
|
||||||
|
// the object will be uploaded in a single request.
|
||||||
|
//
|
||||||
|
// ChunkSize will default to a reasonable value. Any custom configuration
|
||||||
|
// must be done before the first Write call.
|
||||||
|
ChunkSize int
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
o *ObjectHandle
|
||||||
|
|
||||||
|
opened bool
|
||||||
|
pw *io.PipeWriter
|
||||||
|
|
||||||
|
donec chan struct{} // closed after err and obj are set.
|
||||||
|
err error
|
||||||
|
obj *ObjectAttrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) open() error {
|
||||||
|
attrs := w.ObjectAttrs
|
||||||
|
// Check the developer didn't change the object Name (this is unfortunate, but
|
||||||
|
// we don't want to store an object under the wrong name).
|
||||||
|
if attrs.Name != w.o.object {
|
||||||
|
return fmt.Errorf("storage: Writer.Name %q does not match object name %q", attrs.Name, w.o.object)
|
||||||
|
}
|
||||||
|
if !utf8.ValidString(attrs.Name) {
|
||||||
|
return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name)
|
||||||
|
}
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
w.pw = pw
|
||||||
|
w.opened = true
|
||||||
|
|
||||||
|
if w.ChunkSize < 0 {
|
||||||
|
return errors.New("storage: Writer.ChunkSize must non-negative")
|
||||||
|
}
|
||||||
|
mediaOpts := []googleapi.MediaOption{
|
||||||
|
googleapi.ChunkSize(w.ChunkSize),
|
||||||
|
}
|
||||||
|
if c := attrs.ContentType; c != "" {
|
||||||
|
mediaOpts = append(mediaOpts, googleapi.ContentType(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(w.donec)
|
||||||
|
|
||||||
|
call := w.o.c.raw.Objects.Insert(w.o.bucket, attrs.toRawObject(w.o.bucket)).
|
||||||
|
Media(pr, mediaOpts...).
|
||||||
|
Projection("full").
|
||||||
|
Context(w.ctx)
|
||||||
|
if err := setEncryptionHeaders(call.Header(), w.o.encryptionKey, false); err != nil {
|
||||||
|
w.err = err
|
||||||
|
pr.CloseWithError(w.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resp *raw.Object
|
||||||
|
err := applyConds("NewWriter", w.o.gen, w.o.conds, call)
|
||||||
|
if err == nil {
|
||||||
|
err = runWithRetry(w.ctx, func() error { resp, err = call.Do(); return err })
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
w.err = err
|
||||||
|
pr.CloseWithError(w.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.obj = newObject(resp)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write appends to w. It implements the io.Writer interface.
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
if !w.opened {
|
||||||
|
if err := w.open(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
if err := w.open(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attrs returns metadata about a successfully-written object.
|
||||||
|
// It's only valid to call it after Close returns nil.
|
||||||
|
func (w *Writer) Attrs() *ObjectAttrs {
|
||||||
|
return w.obj
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||||
|
|
||||||
|
### Before you contribute
|
||||||
|
Before we can use your code, you must sign the
|
||||||
|
[Google Individual Contributor License Agreement]
|
||||||
|
(https://cla.developers.google.com/about/google-individual)
|
||||||
|
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||||
|
copyright to your changes, even after your contribution becomes part of our
|
||||||
|
codebase, so we need your permission to use and distribute your code. We also
|
||||||
|
need to be sure of various other things—for instance that you'll tell us if you
|
||||||
|
know that your code infringes on other people's patents. You don't have to sign
|
||||||
|
the CLA until after you've submitted your code for review and a member has
|
||||||
|
approved it, but you must do it before we can put your code into our codebase.
|
||||||
|
Before you start working on a larger contribution, you should get in touch with
|
||||||
|
us first through the issue tracker with your idea so that we can help out and
|
||||||
|
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||||
|
frustration later on.
|
||||||
|
|
||||||
|
### Code reviews
|
||||||
|
All submissions, including submissions by project members, require review. We
|
||||||
|
use Github pull requests for this purpose.
|
||||||
|
|
||||||
|
### The small print
|
||||||
|
Contributions made by corporations are covered by a different agreement than
|
||||||
|
the one above, the
|
||||||
|
[Software Grant and Corporate Contributor License Agreement]
|
||||||
|
(https://cla.developers.google.com/about/google-corporate).
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2016, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,24 @@
|
||||||
|
Google API Extensions for Go
|
||||||
|
============================
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/googleapis/gax-go.svg?branch=master)](https://travis-ci.org/googleapis/gax-go)
|
||||||
|
[![Code Coverage](https://img.shields.io/codecov/c/github/googleapis/gax-go.svg)](https://codecov.io/github/googleapis/gax-go)
|
||||||
|
|
||||||
|
Google API Extensions for Go (gax-go) is a set of modules which aids the
|
||||||
|
development of APIs for clients and servers based on `gRPC` and Google API
|
||||||
|
conventions.
|
||||||
|
|
||||||
|
Application code will rarely need to use this library directly,
|
||||||
|
but the code generated automatically from API definition files can use it
|
||||||
|
to simplify code generation and to provide more convenient and idiomatic API surface.
|
||||||
|
|
||||||
|
**This project is currently experimental and not supported.**
|
||||||
|
|
||||||
|
Go Versions
|
||||||
|
===========
|
||||||
|
This library requires Go 1.6 or above.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/master/LICENSE)
|
||||||
|
for more information.
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2016, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||||
|
// CallOption works by modifying relevant fields of CallSettings.
|
||||||
|
type CallOption interface {
|
||||||
|
// Resolve applies the option by modifying cs.
|
||||||
|
Resolve(cs *CallSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retryer is used by Invoke to determine retry behavior.
|
||||||
|
type Retryer interface {
|
||||||
|
// Retry reports whether a request should be retriedand how long to pause before retrying
|
||||||
|
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
|
||||||
|
Retry(err error) (pause time.Duration, shouldRetry bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryerOption func() Retryer
|
||||||
|
|
||||||
|
func (o retryerOption) Resolve(s *CallSettings) {
|
||||||
|
s.Retry = o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRetry sets CallSettings.Retry to fn.
|
||||||
|
func WithRetry(fn func() Retryer) CallOption {
|
||||||
|
return retryerOption(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnCodes returns a Retryer that retries if and only if
|
||||||
|
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||||
|
// Pause times between retries are specified by bo.
|
||||||
|
//
|
||||||
|
// bo is only used for its parameters; each Retryer has its own copy.
|
||||||
|
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
||||||
|
return &boRetryer{
|
||||||
|
backoff: bo,
|
||||||
|
codes: append([]codes.Code(nil), cc...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type boRetryer struct {
|
||||||
|
backoff Backoff
|
||||||
|
codes []codes.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
||||||
|
c := grpc.Code(err)
|
||||||
|
for _, rc := range r.codes {
|
||||||
|
if c == rc {
|
||||||
|
return r.backoff.Pause(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backoff implements exponential backoff.
|
||||||
|
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||||
|
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||||
|
// but is capped at Max.
|
||||||
|
type Backoff struct {
|
||||||
|
// Initial is the initial value of the retry envelope, defaults to 1 second.
|
||||||
|
Initial time.Duration
|
||||||
|
|
||||||
|
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
|
||||||
|
Max time.Duration
|
||||||
|
|
||||||
|
// Multiplier is the factor by which the retry envelope increases.
|
||||||
|
// It should be greater than 1 and defaults to 2.
|
||||||
|
Multiplier float64
|
||||||
|
|
||||||
|
// cur is the current retry envelope
|
||||||
|
cur time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bo *Backoff) Pause() time.Duration {
|
||||||
|
if bo.Initial == 0 {
|
||||||
|
bo.Initial = time.Second
|
||||||
|
}
|
||||||
|
if bo.cur == 0 {
|
||||||
|
bo.cur = bo.Initial
|
||||||
|
}
|
||||||
|
if bo.Max == 0 {
|
||||||
|
bo.Max = 30 * time.Second
|
||||||
|
}
|
||||||
|
if bo.Multiplier < 1 {
|
||||||
|
bo.Multiplier = 2
|
||||||
|
}
|
||||||
|
d := time.Duration(rand.Int63n(int64(bo.cur)))
|
||||||
|
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
|
||||||
|
if bo.cur > bo.Max {
|
||||||
|
bo.cur = bo.Max
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallSettings struct {
|
||||||
|
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||||
|
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||||
|
Retry func() Retryer
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2016, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Package gax contains a set of modules which aid the development of APIs
|
||||||
|
// for clients and servers based on gRPC and Google API conventions.
|
||||||
|
//
|
||||||
|
// Application code will rarely need to use this library directly.
|
||||||
|
// However, code generated automatically from API definition files can use it
|
||||||
|
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||||
|
//
|
||||||
|
// This project is currently experimental and not supported.
|
||||||
|
package gax
|
||||||
|
|
||||||
|
const Version = "0.1.0"
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2016, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A user defined call stub.
|
||||||
|
type APICall func(context.Context) error
|
||||||
|
|
||||||
|
// Invoke calls the given APICall,
|
||||||
|
// performing retries as specified by opts, if any.
|
||||||
|
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
||||||
|
var settings CallSettings
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.Resolve(&settings)
|
||||||
|
}
|
||||||
|
return invoke(ctx, call, settings, Sleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||||
|
// If interrupted, Sleep returns ctx.Err().
|
||||||
|
func Sleep(ctx context.Context, d time.Duration) error {
|
||||||
|
t := time.NewTimer(d)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Stop()
|
||||||
|
return ctx.Err()
|
||||||
|
case <-t.C:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sleeper func(ctx context.Context, d time.Duration) error
|
||||||
|
|
||||||
|
// invoke implements Invoke, taking an additional sleeper argument for testing.
|
||||||
|
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
||||||
|
var retryer Retryer
|
||||||
|
for {
|
||||||
|
err := call(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if settings.Retry == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if retryer == nil {
|
||||||
|
if r := settings.Retry(); r != nil {
|
||||||
|
retryer = r
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d, ok := retryer.Retry(err); !ok {
|
||||||
|
return err
|
||||||
|
} else if err = sp(ctx, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright 2016, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type matcher interface {
|
||||||
|
match([]string) (int, error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
|
matcher
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type labelMatcher string
|
||||||
|
|
||||||
|
func (ls labelMatcher) match(segments []string) (int, error) {
|
||||||
|
if len(segments) == 0 {
|
||||||
|
return 0, fmt.Errorf("expected %s but no more segments found", ls)
|
||||||
|
}
|
||||||
|
if segments[0] != string(ls) {
|
||||||
|
return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls labelMatcher) String() string {
|
||||||
|
return string(ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wildcardMatcher int
|
||||||
|
|
||||||
|
func (wm wildcardMatcher) match(segments []string) (int, error) {
|
||||||
|
if len(segments) == 0 {
|
||||||
|
return 0, errors.New("no more segments found")
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm wildcardMatcher) String() string {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathWildcardMatcher int
|
||||||
|
|
||||||
|
func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
|
||||||
|
length := len(segments) - int(pwm)
|
||||||
|
if length <= 0 {
|
||||||
|
return 0, errors.New("not sufficient segments are supplied for path wildcard")
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pwm pathWildcardMatcher) String() string {
|
||||||
|
return "**"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
Pos int
|
||||||
|
Template string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe ParseError) Error() string {
|
||||||
|
return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathTemplate manages the template to build and match with paths used
|
||||||
|
// by API services. It holds a template and variable names in it, and
|
||||||
|
// it can extract matched patterns from a path string or build a path
|
||||||
|
// string from a binding.
|
||||||
|
//
|
||||||
|
// See http.proto in github.com/googleapis/googleapis/ for the details of
|
||||||
|
// the template syntax.
|
||||||
|
type PathTemplate struct {
|
||||||
|
segments []segment
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPathTemplate parses a path template, and returns a PathTemplate
|
||||||
|
// instance if successful.
|
||||||
|
func NewPathTemplate(template string) (*PathTemplate, error) {
|
||||||
|
return parsePathTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCompilePathTemplate is like NewPathTemplate but panics if the
|
||||||
|
// expression cannot be parsed. It simplifies safe initialization of
|
||||||
|
// global variables holding compiled regular expressions.
|
||||||
|
func MustCompilePathTemplate(template string) *PathTemplate {
|
||||||
|
pt, err := NewPathTemplate(template)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match attempts to match the given path with the template, and returns
|
||||||
|
// the mapping of the variable name to the matched pattern string.
|
||||||
|
func (pt *PathTemplate) Match(path string) (map[string]string, error) {
|
||||||
|
paths := strings.Split(path, "/")
|
||||||
|
values := map[string]string{}
|
||||||
|
for _, segment := range pt.segments {
|
||||||
|
length, err := segment.match(paths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if segment.name != "" {
|
||||||
|
value := strings.Join(paths[:length], "/")
|
||||||
|
if oldValue, ok := values[segment.name]; ok {
|
||||||
|
values[segment.name] = oldValue + "/" + value
|
||||||
|
} else {
|
||||||
|
values[segment.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths = paths[length:]
|
||||||
|
}
|
||||||
|
if len(paths) != 0 {
|
||||||
|
return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render creates a path string from its template and the binding from
|
||||||
|
// the variable name to the value.
|
||||||
|
func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
|
||||||
|
result := make([]string, 0, len(pt.segments))
|
||||||
|
var lastVariableName string
|
||||||
|
for _, segment := range pt.segments {
|
||||||
|
name := segment.name
|
||||||
|
if lastVariableName != "" && name == lastVariableName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastVariableName = name
|
||||||
|
if name == "" {
|
||||||
|
result = append(result, segment.String())
|
||||||
|
} else if value, ok := binding[name]; ok {
|
||||||
|
result = append(result, value)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("%s is not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
built := strings.Join(result, "/")
|
||||||
|
return built, nil
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
// Copyright 2016, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This parser follows the syntax of path templates, from
|
||||||
|
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
|
||||||
|
// The differences are that there is no custom verb, we allow the initial slash
|
||||||
|
// to be absent, and that we are not strict as
|
||||||
|
// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
|
||||||
|
// literals.
|
||||||
|
|
||||||
|
type pathTemplateParser struct {
|
||||||
|
r *strings.Reader
|
||||||
|
runeCount int // the number of the current rune in the original string
|
||||||
|
nextVar int // the number to use for the next unnamed variable
|
||||||
|
seenName map[string]bool // names we've seen already
|
||||||
|
seenPathWildcard bool // have we seen "**" already?
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePathTemplate(template string) (pt *PathTemplate, err error) {
|
||||||
|
p := &pathTemplateParser{
|
||||||
|
r: strings.NewReader(template),
|
||||||
|
seenName: map[string]bool{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle panics with strings like errors.
|
||||||
|
// See pathTemplateParser.error, below.
|
||||||
|
defer func() {
|
||||||
|
if x := recover(); x != nil {
|
||||||
|
errmsg, ok := x.(errString)
|
||||||
|
if !ok {
|
||||||
|
panic(x)
|
||||||
|
}
|
||||||
|
pt = nil
|
||||||
|
err = ParseError{p.runeCount, template, string(errmsg)}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
segs := p.template()
|
||||||
|
// If there is a path wildcard, set its length. We can't do this
|
||||||
|
// until we know how many segments we've got all together.
|
||||||
|
for i, seg := range segs {
|
||||||
|
if _, ok := seg.matcher.(pathWildcardMatcher); ok {
|
||||||
|
segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &PathTemplate{segments: segs}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to indicate errors "thrown" by this parser. We don't use string because
|
||||||
|
// many parts of the standard library panic with strings.
|
||||||
|
type errString string
|
||||||
|
|
||||||
|
// Terminates parsing immediately with an error.
|
||||||
|
func (p *pathTemplateParser) error(msg string) {
|
||||||
|
panic(errString(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template = [ "/" ] Segments
|
||||||
|
func (p *pathTemplateParser) template() []segment {
|
||||||
|
var segs []segment
|
||||||
|
if p.consume('/') {
|
||||||
|
// Initial '/' needs an initial empty matcher.
|
||||||
|
segs = append(segs, segment{matcher: labelMatcher("")})
|
||||||
|
}
|
||||||
|
return append(segs, p.segments("")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segments = Segment { "/" Segment }
|
||||||
|
func (p *pathTemplateParser) segments(name string) []segment {
|
||||||
|
var segs []segment
|
||||||
|
for {
|
||||||
|
subsegs := p.segment(name)
|
||||||
|
segs = append(segs, subsegs...)
|
||||||
|
if !p.consume('/') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment = "*" | "**" | LITERAL | Variable
|
||||||
|
func (p *pathTemplateParser) segment(name string) []segment {
|
||||||
|
if p.consume('*') {
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("$%d", p.nextVar)
|
||||||
|
p.nextVar++
|
||||||
|
}
|
||||||
|
if p.consume('*') {
|
||||||
|
if p.seenPathWildcard {
|
||||||
|
p.error("multiple '**' disallowed")
|
||||||
|
}
|
||||||
|
p.seenPathWildcard = true
|
||||||
|
// We'll change 0 to the right number at the end.
|
||||||
|
return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
return []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
if p.consume('{') {
|
||||||
|
if name != "" {
|
||||||
|
p.error("recursive named bindings are not allowed")
|
||||||
|
}
|
||||||
|
return p.variable()
|
||||||
|
}
|
||||||
|
return []segment{{name: name, matcher: labelMatcher(p.literal())}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable = "{" FieldPath [ "=" Segments ] "}"
|
||||||
|
// "{" is already consumed.
|
||||||
|
func (p *pathTemplateParser) variable() []segment {
|
||||||
|
// Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
|
||||||
|
name := p.literal()
|
||||||
|
if p.seenName[name] {
|
||||||
|
p.error(name + " appears multiple times")
|
||||||
|
}
|
||||||
|
p.seenName[name] = true
|
||||||
|
var segs []segment
|
||||||
|
if p.consume('=') {
|
||||||
|
segs = p.segments(name)
|
||||||
|
} else {
|
||||||
|
// "{var}" is equivalent to "{var=*}"
|
||||||
|
segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
if !p.consume('}') {
|
||||||
|
p.error("expected '}'")
|
||||||
|
}
|
||||||
|
return segs
|
||||||
|
}
|
||||||
|
|
||||||
|
// A literal is any sequence of characters other than a few special ones.
|
||||||
|
// The list of stop characters is not quite the same as in the template RFC.
|
||||||
|
func (p *pathTemplateParser) literal() string {
|
||||||
|
lit := p.consumeUntil("/*}{=")
|
||||||
|
if lit == "" {
|
||||||
|
p.error("empty literal")
|
||||||
|
}
|
||||||
|
return lit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read runes until EOF or one of the runes in stopRunes is encountered.
|
||||||
|
// If the latter, unread the stop rune. Return the accumulated runes as a string.
|
||||||
|
func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
|
||||||
|
var runes []rune
|
||||||
|
for {
|
||||||
|
r, ok := p.readRune()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.IndexRune(stopRunes, r) >= 0 {
|
||||||
|
p.unreadRune()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the next rune is r, consume it and return true.
|
||||||
|
// Otherwise, leave the input unchanged and return false.
|
||||||
|
func (p *pathTemplateParser) consume(r rune) bool {
|
||||||
|
rr, ok := p.readRune()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r == rr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.unreadRune()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next rune from the input. Return it.
|
||||||
|
// The second return value is false at EOF.
|
||||||
|
func (p *pathTemplateParser) readRune() (rune, bool) {
|
||||||
|
r, _, err := p.r.ReadRune()
|
||||||
|
if err == io.EOF {
|
||||||
|
return r, false
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
p.error(err.Error())
|
||||||
|
}
|
||||||
|
p.runeCount++
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the last rune that was read back on the input.
|
||||||
|
func (p *pathTemplateParser) unreadRune() {
|
||||||
|
if err := p.r.UnreadRune(); err != nil {
|
||||||
|
p.error(err.Error())
|
||||||
|
}
|
||||||
|
p.runeCount--
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
|
||||||
|
package ctxhttp // import "golang.org/x/net/context/ctxhttp"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Do sends an HTTP request with the provided http.Client and returns
|
||||||
|
// an HTTP response.
|
||||||
|
//
|
||||||
|
// If the client is nil, http.DefaultClient is used.
|
||||||
|
//
|
||||||
|
// The provided ctx must be non-nil. If it is canceled or times out,
|
||||||
|
// ctx.Err() will be returned.
|
||||||
|
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
|
if client == nil {
|
||||||
|
client = http.DefaultClient
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req.WithContext(ctx))
|
||||||
|
// If we got an error, and the context has been canceled,
|
||||||
|
// the context's error is probably more useful.
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err = ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues a GET request via the Do function.
|
||||||
|
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head issues a HEAD request via the Do function.
|
||||||
|
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post issues a POST request via the Do function.
|
||||||
|
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", bodyType)
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm issues a POST request via the Do function.
|
||||||
|
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
|
||||||
|
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package ctxhttp // import "golang.org/x/net/context/ctxhttp"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nop() {}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testHookContextDoneBeforeHeaders = nop
|
||||||
|
testHookDoReturned = nop
|
||||||
|
testHookDidBodyClose = nop
|
||||||
|
)
|
||||||
|
|
||||||
|
// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
|
||||||
|
// If the client is nil, http.DefaultClient is used.
|
||||||
|
// If the context is canceled or times out, ctx.Err() will be returned.
|
||||||
|
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
|
if client == nil {
|
||||||
|
client = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(djd): Respect any existing value of req.Cancel.
|
||||||
|
cancel := make(chan struct{})
|
||||||
|
req.Cancel = cancel
|
||||||
|
|
||||||
|
type responseAndError struct {
|
||||||
|
resp *http.Response
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
result := make(chan responseAndError, 1)
|
||||||
|
|
||||||
|
// Make local copies of test hooks closed over by goroutines below.
|
||||||
|
// Prevents data races in tests.
|
||||||
|
testHookDoReturned := testHookDoReturned
|
||||||
|
testHookDidBodyClose := testHookDidBodyClose
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
testHookDoReturned()
|
||||||
|
result <- responseAndError{resp, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
testHookContextDoneBeforeHeaders()
|
||||||
|
close(cancel)
|
||||||
|
// Clean up after the goroutine calling client.Do:
|
||||||
|
go func() {
|
||||||
|
if r := <-result; r.resp != nil {
|
||||||
|
testHookDidBodyClose()
|
||||||
|
r.resp.Body.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case r := <-result:
|
||||||
|
var err error
|
||||||
|
resp, err = r.resp, r.err
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
close(cancel)
|
||||||
|
case <-c:
|
||||||
|
// The response's Body is closed.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resp.Body = ¬ifyingReader{resp.Body, c}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues a GET request via the Do function.
|
||||||
|
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head issues a HEAD request via the Do function.
|
||||||
|
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post issues a POST request via the Do function.
|
||||||
|
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", bodyType)
|
||||||
|
return Do(ctx, client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm issues a POST request via the Do function.
|
||||||
|
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
|
||||||
|
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifyingReader is an io.ReadCloser that closes the notify channel after
|
||||||
|
// Close is called or a Read fails on the underlying ReadCloser.
|
||||||
|
type notifyingReader struct {
|
||||||
|
io.ReadCloser
|
||||||
|
notify chan<- struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *notifyingReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := r.ReadCloser.Read(p)
|
||||||
|
if err != nil && r.notify != nil {
|
||||||
|
close(r.notify)
|
||||||
|
r.notify = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *notifyingReader) Close() error {
|
||||||
|
err := r.ReadCloser.Close()
|
||||||
|
if r.notify != nil {
|
||||||
|
close(r.notify)
|
||||||
|
r.notify = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set at init time by appenginevm_hook.go. If true, we are on App Engine Managed VMs.
|
||||||
|
var appengineVM bool
|
||||||
|
|
||||||
|
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
|
||||||
|
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
|
||||||
|
|
||||||
|
// AppEngineTokenSource returns a token source that fetches tokens
|
||||||
|
// issued to the current App Engine application's service account.
|
||||||
|
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||||
|
// that involves user accounts, see oauth2.Config instead.
|
||||||
|
//
|
||||||
|
// The provided context must have come from appengine.NewContext.
|
||||||
|
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||||
|
if appengineTokenFunc == nil {
|
||||||
|
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||||
|
}
|
||||||
|
scopes := append([]string{}, scope...)
|
||||||
|
sort.Strings(scopes)
|
||||||
|
return &appEngineTokenSource{
|
||||||
|
ctx: ctx,
|
||||||
|
scopes: scopes,
|
||||||
|
key: strings.Join(scopes, " "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||||
|
var (
|
||||||
|
aeTokensMu sync.Mutex
|
||||||
|
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenLock struct {
|
||||||
|
mu sync.Mutex // guards t; held while fetching or updating t
|
||||||
|
t *oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type appEngineTokenSource struct {
|
||||||
|
ctx context.Context
|
||||||
|
scopes []string
|
||||||
|
key string // to aeTokens map; space-separated scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
if appengineTokenFunc == nil {
|
||||||
|
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||||
|
}
|
||||||
|
|
||||||
|
aeTokensMu.Lock()
|
||||||
|
tok, ok := aeTokens[ts.key]
|
||||||
|
if !ok {
|
||||||
|
tok = &tokenLock{}
|
||||||
|
aeTokens[ts.key] = tok
|
||||||
|
}
|
||||||
|
aeTokensMu.Unlock()
|
||||||
|
|
||||||
|
tok.mu.Lock()
|
||||||
|
defer tok.mu.Unlock()
|
||||||
|
if tok.t.Valid() {
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
||||||
|
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tok.t = &oauth2.Token{
|
||||||
|
AccessToken: access,
|
||||||
|
Expiry: exp,
|
||||||
|
}
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import "google.golang.org/appengine"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineTokenFunc = appengine.AccessToken
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import "google.golang.org/appengine"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineVM = true
|
||||||
|
appengineTokenFunc = appengine.AccessToken
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"cloud.google.com/go/compute/metadata"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultClient returns an HTTP Client that uses the
|
||||||
|
// DefaultTokenSource to obtain authentication credentials.
|
||||||
|
//
|
||||||
|
// This client should be used when developing services
|
||||||
|
// that run on Google App Engine or Google Compute Engine
|
||||||
|
// and use "Application Default Credentials."
|
||||||
|
//
|
||||||
|
// For more details, see:
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||||
|
//
|
||||||
|
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
||||||
|
ts, err := DefaultTokenSource(ctx, scope...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return oauth2.NewClient(ctx, ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTokenSource is a token source that uses
|
||||||
|
// "Application Default Credentials".
|
||||||
|
//
|
||||||
|
// It looks for credentials in the following places,
|
||||||
|
// preferring the first location found:
|
||||||
|
//
|
||||||
|
// 1. A JSON file whose path is specified by the
|
||||||
|
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||||
|
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||||
|
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||||
|
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||||
|
// 3. On Google App Engine it uses the appengine.AccessToken function.
|
||||||
|
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
|
||||||
|
// credentials from the metadata server.
|
||||||
|
// (In this final case any provided scopes are ignored.)
|
||||||
|
//
|
||||||
|
// For more details, see:
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||||
|
//
|
||||||
|
func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
|
||||||
|
// First, try the environment variable.
|
||||||
|
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||||
|
if filename := os.Getenv(envVar); filename != "" {
|
||||||
|
ts, err := tokenSourceFromFile(ctx, filename, scope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, try a well-known file.
|
||||||
|
filename := wellKnownFile()
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
if err == nil {
|
||||||
|
ts, err2 := tokenSourceFromFile(ctx, filename, scope)
|
||||||
|
if err2 == nil {
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
err = err2
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
err = nil // ignore this error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third, if we're on Google App Engine use those credentials.
|
||||||
|
if appengineTokenFunc != nil && !appengineVM {
|
||||||
|
return AppEngineTokenSource(ctx, scope...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth, if we're on Google Compute Engine use the metadata server.
|
||||||
|
if metadata.OnGCE() {
|
||||||
|
return ComputeTokenSource(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// None are found; return helpful error.
|
||||||
|
const url = "https://developers.google.com/accounts/docs/application-default-credentials"
|
||||||
|
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wellKnownFile() string {
|
||||||
|
const f = "application_default_credentials.json"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
|
||||||
|
}
|
||||||
|
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var f credentialsFile
|
||||||
|
if err := json.Unmarshal(b, &f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f.tokenSource(ctx, scopes)
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package google provides support for making OAuth2 authorized and
|
||||||
|
// authenticated HTTP requests to Google APIs.
|
||||||
|
// It supports the Web server flow, client-side credentials, service accounts,
|
||||||
|
// Google Compute Engine service accounts, and Google App Engine service
|
||||||
|
// accounts.
|
||||||
|
//
|
||||||
|
// For more information, please read
|
||||||
|
// https://developers.google.com/accounts/docs/OAuth2
|
||||||
|
// and
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||||
|
package google // import "golang.org/x/oauth2/google"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"cloud.google.com/go/compute/metadata"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||||
|
const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
||||||
|
|
||||||
|
// ConfigFromJSON uses a Google Developers Console client_credentials.json
|
||||||
|
// file to construct a config.
|
||||||
|
// client_credentials.json can be downloaded from
|
||||||
|
// https://console.developers.google.com, under "Credentials". Download the Web
|
||||||
|
// application credentials in the JSON format and provide the contents of the
|
||||||
|
// file as jsonKey.
|
||||||
|
func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
|
||||||
|
type cred struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
RedirectURIs []string `json:"redirect_uris"`
|
||||||
|
AuthURI string `json:"auth_uri"`
|
||||||
|
TokenURI string `json:"token_uri"`
|
||||||
|
}
|
||||||
|
var j struct {
|
||||||
|
Web *cred `json:"web"`
|
||||||
|
Installed *cred `json:"installed"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonKey, &j); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var c *cred
|
||||||
|
switch {
|
||||||
|
case j.Web != nil:
|
||||||
|
c = j.Web
|
||||||
|
case j.Installed != nil:
|
||||||
|
c = j.Installed
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no credentials found")
|
||||||
|
}
|
||||||
|
if len(c.RedirectURIs) < 1 {
|
||||||
|
return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json")
|
||||||
|
}
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: c.ClientID,
|
||||||
|
ClientSecret: c.ClientSecret,
|
||||||
|
RedirectURL: c.RedirectURIs[0],
|
||||||
|
Scopes: scope,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: c.AuthURI,
|
||||||
|
TokenURL: c.TokenURI,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
||||||
|
// the credentials that authorize and authenticate the requests.
|
||||||
|
// Create a service account on "Credentials" for your project at
|
||||||
|
// https://console.developers.google.com to download a JSON key file.
|
||||||
|
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
||||||
|
var f credentialsFile
|
||||||
|
if err := json.Unmarshal(jsonKey, &f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.Type != serviceAccountKey {
|
||||||
|
return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey)
|
||||||
|
}
|
||||||
|
scope = append([]string(nil), scope...) // copy
|
||||||
|
return f.jwtConfig(scope), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON key file types.
|
||||||
|
const (
|
||||||
|
serviceAccountKey = "service_account"
|
||||||
|
userCredentialsKey = "authorized_user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// credentialsFile is the unmarshalled representation of a credentials file.
|
||||||
|
type credentialsFile struct {
|
||||||
|
Type string `json:"type"` // serviceAccountKey or userCredentialsKey
|
||||||
|
|
||||||
|
// Service Account fields
|
||||||
|
ClientEmail string `json:"client_email"`
|
||||||
|
PrivateKeyID string `json:"private_key_id"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
TokenURL string `json:"token_uri"`
|
||||||
|
|
||||||
|
// User Credential fields
|
||||||
|
// (These typically come from gcloud auth.)
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config {
|
||||||
|
cfg := &jwt.Config{
|
||||||
|
Email: f.ClientEmail,
|
||||||
|
PrivateKey: []byte(f.PrivateKey),
|
||||||
|
PrivateKeyID: f.PrivateKeyID,
|
||||||
|
Scopes: scopes,
|
||||||
|
TokenURL: f.TokenURL,
|
||||||
|
}
|
||||||
|
if cfg.TokenURL == "" {
|
||||||
|
cfg.TokenURL = JWTTokenURL
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) {
|
||||||
|
switch f.Type {
|
||||||
|
case serviceAccountKey:
|
||||||
|
cfg := f.jwtConfig(scopes)
|
||||||
|
return cfg.TokenSource(ctx), nil
|
||||||
|
case userCredentialsKey:
|
||||||
|
cfg := &oauth2.Config{
|
||||||
|
ClientID: f.ClientID,
|
||||||
|
ClientSecret: f.ClientSecret,
|
||||||
|
Scopes: scopes,
|
||||||
|
Endpoint: Endpoint,
|
||||||
|
}
|
||||||
|
tok := &oauth2.Token{RefreshToken: f.RefreshToken}
|
||||||
|
return cfg.TokenSource(ctx, tok), nil
|
||||||
|
case "":
|
||||||
|
return nil, errors.New("missing 'type' field in credentials")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown credential type: %q", f.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeTokenSource returns a token source that fetches access tokens
|
||||||
|
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
||||||
|
// this token source if your program is running on a GCE instance.
|
||||||
|
// If no account is specified, "default" is used.
|
||||||
|
// Further information about retrieving access tokens from the GCE metadata
|
||||||
|
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
||||||
|
func ComputeTokenSource(account string) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, computeSource{account: account})
|
||||||
|
}
|
||||||
|
|
||||||
|
type computeSource struct {
|
||||||
|
account string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs computeSource) Token() (*oauth2.Token, error) {
|
||||||
|
if !metadata.OnGCE() {
|
||||||
|
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
|
||||||
|
}
|
||||||
|
acct := cs.account
|
||||||
|
if acct == "" {
|
||||||
|
acct = "default"
|
||||||
|
}
|
||||||
|
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresInSec int `json:"expires_in"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
|
||||||
|
}
|
||||||
|
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
|
||||||
|
}
|
||||||
|
return &oauth2.Token{
|
||||||
|
AccessToken: res.AccessToken,
|
||||||
|
TokenType: res.TokenType,
|
||||||
|
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"golang.org/x/oauth2/jws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON
|
||||||
|
// key file to read the credentials that authorize and authenticate the
|
||||||
|
// requests, and returns a TokenSource that does not use any OAuth2 flow but
|
||||||
|
// instead creates a JWT and sends that as the access token.
|
||||||
|
// The audience is typically a URL that specifies the scope of the credentials.
|
||||||
|
//
|
||||||
|
// Note that this is not a standard OAuth flow, but rather an
|
||||||
|
// optimization supported by a few Google services.
|
||||||
|
// Unless you know otherwise, you should use JWTConfigFromJSON instead.
|
||||||
|
func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) {
|
||||||
|
cfg, err := JWTConfigFromJSON(jsonKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not parse JSON key: %v", err)
|
||||||
|
}
|
||||||
|
pk, err := internal.ParseKey(cfg.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not parse key: %v", err)
|
||||||
|
}
|
||||||
|
ts := &jwtAccessTokenSource{
|
||||||
|
email: cfg.Email,
|
||||||
|
audience: audience,
|
||||||
|
pk: pk,
|
||||||
|
pkID: cfg.PrivateKeyID,
|
||||||
|
}
|
||||||
|
tok, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return oauth2.ReuseTokenSource(tok, ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtAccessTokenSource struct {
|
||||||
|
email, audience string
|
||||||
|
pk *rsa.PrivateKey
|
||||||
|
pkID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
iat := time.Now()
|
||||||
|
exp := iat.Add(time.Hour)
|
||||||
|
cs := &jws.ClaimSet{
|
||||||
|
Iss: ts.email,
|
||||||
|
Sub: ts.email,
|
||||||
|
Aud: ts.audience,
|
||||||
|
Iat: iat.Unix(),
|
||||||
|
Exp: exp.Unix(),
|
||||||
|
}
|
||||||
|
hdr := &jws.Header{
|
||||||
|
Algorithm: "RS256",
|
||||||
|
Typ: "JWT",
|
||||||
|
KeyID: string(ts.pkID),
|
||||||
|
}
|
||||||
|
msg, err := jws.Encode(hdr, cs, ts.pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not encode JWT: %v", err)
|
||||||
|
}
|
||||||
|
return &oauth2.Token{AccessToken: msg, TokenType: "Bearer", Expiry: exp}, nil
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdkCredentials struct {
|
||||||
|
Data []struct {
|
||||||
|
Credential struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenExpiry *time.Time `json:"token_expiry"`
|
||||||
|
} `json:"credential"`
|
||||||
|
Key struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"key"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An SDKConfig provides access to tokens from an account already
|
||||||
|
// authorized via the Google Cloud SDK.
|
||||||
|
type SDKConfig struct {
|
||||||
|
conf oauth2.Config
|
||||||
|
initialToken *oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
|
||||||
|
// account. If account is empty, the account currently active in
|
||||||
|
// Google Cloud SDK properties is used.
|
||||||
|
// Google Cloud SDK credentials must be created by running `gcloud auth`
|
||||||
|
// before using this function.
|
||||||
|
// The Google Cloud SDK is available at https://cloud.google.com/sdk/.
|
||||||
|
func NewSDKConfig(account string) (*SDKConfig, error) {
|
||||||
|
configPath, err := sdkConfigPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err)
|
||||||
|
}
|
||||||
|
credentialsPath := filepath.Join(configPath, "credentials")
|
||||||
|
f, err := os.Open(credentialsPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var c sdkCredentials
|
||||||
|
if err := json.NewDecoder(f).Decode(&c); err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err)
|
||||||
|
}
|
||||||
|
if len(c.Data) == 0 {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath)
|
||||||
|
}
|
||||||
|
if account == "" {
|
||||||
|
propertiesPath := filepath.Join(configPath, "properties")
|
||||||
|
f, err := os.Open(propertiesPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ini, err := internal.ParseINI(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
|
||||||
|
}
|
||||||
|
core, ok := ini["core"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini)
|
||||||
|
}
|
||||||
|
active, ok := core["account"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core)
|
||||||
|
}
|
||||||
|
account = active
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range c.Data {
|
||||||
|
if account == "" || d.Key.Account == account {
|
||||||
|
if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no token available for account %q", account)
|
||||||
|
}
|
||||||
|
var expiry time.Time
|
||||||
|
if d.Credential.TokenExpiry != nil {
|
||||||
|
expiry = *d.Credential.TokenExpiry
|
||||||
|
}
|
||||||
|
return &SDKConfig{
|
||||||
|
conf: oauth2.Config{
|
||||||
|
ClientID: d.Credential.ClientID,
|
||||||
|
ClientSecret: d.Credential.ClientSecret,
|
||||||
|
Scopes: strings.Split(d.Key.Scope, " "),
|
||||||
|
Endpoint: Endpoint,
|
||||||
|
RedirectURL: "oob",
|
||||||
|
},
|
||||||
|
initialToken: &oauth2.Token{
|
||||||
|
AccessToken: d.Credential.AccessToken,
|
||||||
|
RefreshToken: d.Credential.RefreshToken,
|
||||||
|
Expiry: expiry,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client using Google Cloud SDK credentials to
|
||||||
|
// authorize requests. The token will auto-refresh as necessary. The
|
||||||
|
// underlying http.RoundTripper will be obtained using the provided
|
||||||
|
// context. The returned client and its Transport should not be
|
||||||
|
// modified.
|
||||||
|
func (c *SDKConfig) Client(ctx context.Context) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
Source: c.TokenSource(ctx),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns an oauth2.TokenSource that retrieve tokens from
|
||||||
|
// Google Cloud SDK credentials using the provided context.
|
||||||
|
// It will returns the current access token stored in the credentials,
|
||||||
|
// and refresh it when it expires, but it won't update the credentials
|
||||||
|
// with the new access token.
|
||||||
|
func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
|
return c.conf.TokenSource(ctx, c.initialToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes are the OAuth 2.0 scopes the current account is authorized for.
|
||||||
|
func (c *SDKConfig) Scopes() []string {
|
||||||
|
return c.conf.Scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
// sdkConfigPath tries to guess where the gcloud config is located.
|
||||||
|
// It can be overridden during tests.
|
||||||
|
var sdkConfigPath = func() (string, error) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil
|
||||||
|
}
|
||||||
|
homeDir := guessUnixHomeDir()
|
||||||
|
if homeDir == "" {
|
||||||
|
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir, ".config", "gcloud"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessUnixHomeDir() string {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return usr.HomeDir
|
||||||
|
}
|
||||||
|
return os.Getenv("HOME")
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jws provides a partial implementation
|
||||||
|
// of JSON Web Signature encoding and decoding.
|
||||||
|
// It exists to support the golang.org/x/oauth2 package.
|
||||||
|
//
|
||||||
|
// See RFC 7515.
|
||||||
|
//
|
||||||
|
// Deprecated: this package is not intended for public use and might be
|
||||||
|
// removed in the future. It exists for internal use only.
|
||||||
|
// Please switch to another JWS package or copy this package into your own
|
||||||
|
// source tree.
|
||||||
|
package jws // import "golang.org/x/oauth2/jws"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClaimSet contains information about the JWT signature including the
|
||||||
|
// permissions being requested (scopes), the target of the token, the issuer,
|
||||||
|
// the time the token was issued, and the lifetime of the token.
|
||||||
|
type ClaimSet struct {
|
||||||
|
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
|
||||||
|
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
|
||||||
|
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
|
||||||
|
Exp int64 `json:"exp"` // the expiration time of the assertion (seconds since Unix epoch)
|
||||||
|
Iat int64 `json:"iat"` // the time the assertion was issued (seconds since Unix epoch)
|
||||||
|
Typ string `json:"typ,omitempty"` // token type (Optional).
|
||||||
|
|
||||||
|
// Email for which the application is requesting delegated access (Optional).
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
|
||||||
|
// The old name of Sub. Client keeps setting Prn to be
|
||||||
|
// complaint with legacy OAuth 2.0 providers. (Optional)
|
||||||
|
Prn string `json:"prn,omitempty"`
|
||||||
|
|
||||||
|
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
|
||||||
|
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
|
||||||
|
PrivateClaims map[string]interface{} `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClaimSet) encode() (string, error) {
|
||||||
|
// Reverting time back for machines whose time is not perfectly in sync.
|
||||||
|
// If client machine's time is in the future according
|
||||||
|
// to Google servers, an access token will not be issued.
|
||||||
|
now := time.Now().Add(-10 * time.Second)
|
||||||
|
if c.Iat == 0 {
|
||||||
|
c.Iat = now.Unix()
|
||||||
|
}
|
||||||
|
if c.Exp == 0 {
|
||||||
|
c.Exp = now.Add(time.Hour).Unix()
|
||||||
|
}
|
||||||
|
if c.Exp < c.Iat {
|
||||||
|
return "", fmt.Errorf("jws: invalid Exp = %v; must be later than Iat = %v", c.Exp, c.Iat)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.PrivateClaims) == 0 {
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal private claim set and then append it to b.
|
||||||
|
prv, err := json.Marshal(c.PrivateClaims)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate public and private claim JSON objects.
|
||||||
|
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", b)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", prv)
|
||||||
|
}
|
||||||
|
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||||
|
b = append(b, prv[1:]...) // Append private claims.
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header represents the header for the signed JWS payloads.
|
||||||
|
type Header struct {
|
||||||
|
// The algorithm used for signature.
|
||||||
|
Algorithm string `json:"alg"`
|
||||||
|
|
||||||
|
// Represents the token type.
|
||||||
|
Typ string `json:"typ"`
|
||||||
|
|
||||||
|
// The optional hint of which key is being used.
|
||||||
|
KeyID string `json:"kid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Header) encode() (string, error) {
|
||||||
|
b, err := json.Marshal(h)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a claim set from a JWS payload.
|
||||||
|
func Decode(payload string) (*ClaimSet, error) {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
s := strings.Split(payload, ".")
|
||||||
|
if len(s) < 2 {
|
||||||
|
// TODO(jbd): Provide more context about the error.
|
||||||
|
return nil, errors.New("jws: invalid token received")
|
||||||
|
}
|
||||||
|
decoded, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c := &ClaimSet{}
|
||||||
|
err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer returns a signature for the given data.
|
||||||
|
type Signer func(data []byte) (sig []byte, err error)
|
||||||
|
|
||||||
|
// EncodeWithSigner encodes a header and claim set with the provided signer.
|
||||||
|
func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error) {
|
||||||
|
head, err := header.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cs, err := c.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ss := fmt.Sprintf("%s.%s", head, cs)
|
||||||
|
sig, err := sg([]byte(ss))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes a signed JWS with provided header and claim set.
|
||||||
|
// This invokes EncodeWithSigner using crypto/rsa.SignPKCS1v15 with the given RSA private key.
|
||||||
|
func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error) {
|
||||||
|
sg := func(data []byte) (sig []byte, err error) {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write(data)
|
||||||
|
return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
|
||||||
|
}
|
||||||
|
return EncodeWithSigner(header, c, sg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify tests whether the provided JWT token's signature was produced by the private key
|
||||||
|
// associated with the supplied public key.
|
||||||
|
func Verify(token string, key *rsa.PublicKey) error {
|
||||||
|
parts := strings.Split(token, ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return errors.New("jws: invalid token received, token must have 3 parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
signedContent := parts[0] + "." + parts[1]
|
||||||
|
signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(signedContent))
|
||||||
|
return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), []byte(signatureString))
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
||||||
|
// known as "two-legged OAuth 2.0".
|
||||||
|
//
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"golang.org/x/oauth2/jws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||||
|
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the configuration for using JWT to fetch tokens,
|
||||||
|
// commonly known as "two-legged OAuth 2.0".
|
||||||
|
type Config struct {
|
||||||
|
// Email is the OAuth client identifier used when communicating with
|
||||||
|
// the configured OAuth provider.
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// PrivateKey contains the contents of an RSA private key or the
|
||||||
|
// contents of a PEM file that contains a private key. The provided
|
||||||
|
// private key is used to sign JWT payloads.
|
||||||
|
// PEM containers with a passphrase are not supported.
|
||||||
|
// Use the following command to convert a PKCS 12 file into a PEM.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||||
|
//
|
||||||
|
PrivateKey []byte
|
||||||
|
|
||||||
|
// PrivateKeyID contains an optional hint indicating which key is being
|
||||||
|
// used.
|
||||||
|
PrivateKeyID string
|
||||||
|
|
||||||
|
// Subject is the optional user to impersonate.
|
||||||
|
Subject string
|
||||||
|
|
||||||
|
// Scopes optionally specifies a list of requested permission scopes.
|
||||||
|
Scopes []string
|
||||||
|
|
||||||
|
// TokenURL is the endpoint required to complete the 2-legged JWT flow.
|
||||||
|
TokenURL string
|
||||||
|
|
||||||
|
// Expires optionally specifies how long the token is valid for.
|
||||||
|
Expires time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a JWT TokenSource using the configuration
|
||||||
|
// in c and the HTTP client from the provided context.
|
||||||
|
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client wrapping the context's
|
||||||
|
// HTTP transport and adding Authorization headers with tokens
|
||||||
|
// obtained from c.
|
||||||
|
//
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||||
|
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwtSource is a source that always does a signed JWT request for a token.
|
||||||
|
// It should typically be wrapped with a reuseTokenSource.
|
||||||
|
type jwtSource struct {
|
||||||
|
ctx context.Context
|
||||||
|
conf *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js jwtSource) Token() (*oauth2.Token, error) {
|
||||||
|
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hc := oauth2.NewClient(js.ctx, nil)
|
||||||
|
claimSet := &jws.ClaimSet{
|
||||||
|
Iss: js.conf.Email,
|
||||||
|
Scope: strings.Join(js.conf.Scopes, " "),
|
||||||
|
Aud: js.conf.TokenURL,
|
||||||
|
}
|
||||||
|
if subject := js.conf.Subject; subject != "" {
|
||||||
|
claimSet.Sub = subject
|
||||||
|
// prn is the old name of sub. Keep setting it
|
||||||
|
// to be compatible with legacy OAuth 2.0 providers.
|
||||||
|
claimSet.Prn = subject
|
||||||
|
}
|
||||||
|
if t := js.conf.Expires; t > 0 {
|
||||||
|
claimSet.Exp = time.Now().Add(t).Unix()
|
||||||
|
}
|
||||||
|
payload, err := jws.Encode(defaultHeader, claimSet, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("grant_type", defaultGrantType)
|
||||||
|
v.Set("assertion", payload)
|
||||||
|
resp, err := hc.PostForm(js.conf.TokenURL, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
if c := resp.StatusCode; c < 200 || c > 299 {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
// tokenRes is the JSON response body.
|
||||||
|
var tokenRes struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenRes.AccessToken,
|
||||||
|
TokenType: tokenRes.TokenType,
|
||||||
|
}
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||||
|
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||||
|
}
|
||||||
|
if v := tokenRes.IDToken; v != "" {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
claimSet, err := jws.Decode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
|
||||||
|
}
|
||||||
|
token.Expiry = time.Unix(claimSet.Exp, 0)
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2011 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackoffStrategy interface {
|
||||||
|
// Pause returns the duration of the next pause and true if the operation should be
|
||||||
|
// retried, or false if no further retries should be attempted.
|
||||||
|
Pause() (time.Duration, bool)
|
||||||
|
|
||||||
|
// Reset restores the strategy to its initial state.
|
||||||
|
Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExponentialBackoff performs exponential backoff as per https://en.wikipedia.org/wiki/Exponential_backoff.
|
||||||
|
// The initial pause time is given by Base.
|
||||||
|
// Once the total pause time exceeds Max, Pause will indicate no further retries.
|
||||||
|
type ExponentialBackoff struct {
|
||||||
|
Base time.Duration
|
||||||
|
Max time.Duration
|
||||||
|
total time.Duration
|
||||||
|
n uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExponentialBackoff) Pause() (time.Duration, bool) {
|
||||||
|
if eb.total > eb.Max {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next pause is selected from randomly from [0, 2^n * Base).
|
||||||
|
d := time.Duration(rand.Int63n((1 << eb.n) * int64(eb.Base)))
|
||||||
|
eb.total += d
|
||||||
|
eb.n++
|
||||||
|
return d, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExponentialBackoff) Reset() {
|
||||||
|
eb.n = 0
|
||||||
|
eb.total = 0
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MediaBuffer buffers data from an io.Reader to support uploading media in retryable chunks.
|
||||||
|
type MediaBuffer struct {
|
||||||
|
media io.Reader
|
||||||
|
|
||||||
|
chunk []byte // The current chunk which is pending upload. The capacity is the chunk size.
|
||||||
|
err error // Any error generated when populating chunk by reading media.
|
||||||
|
|
||||||
|
// The absolute position of chunk in the underlying media.
|
||||||
|
off int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMediaBuffer(media io.Reader, chunkSize int) *MediaBuffer {
|
||||||
|
return &MediaBuffer{media: media, chunk: make([]byte, 0, chunkSize)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk returns the current buffered chunk, the offset in the underlying media
|
||||||
|
// from which the chunk is drawn, and the size of the chunk.
|
||||||
|
// Successive calls to Chunk return the same chunk between calls to Next.
|
||||||
|
func (mb *MediaBuffer) Chunk() (chunk io.Reader, off int64, size int, err error) {
|
||||||
|
// There may already be data in chunk if Next has not been called since the previous call to Chunk.
|
||||||
|
if mb.err == nil && len(mb.chunk) == 0 {
|
||||||
|
mb.err = mb.loadChunk()
|
||||||
|
}
|
||||||
|
return bytes.NewReader(mb.chunk), mb.off, len(mb.chunk), mb.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadChunk will read from media into chunk, up to the capacity of chunk.
|
||||||
|
func (mb *MediaBuffer) loadChunk() error {
|
||||||
|
bufSize := cap(mb.chunk)
|
||||||
|
mb.chunk = mb.chunk[:bufSize]
|
||||||
|
|
||||||
|
read := 0
|
||||||
|
var err error
|
||||||
|
for err == nil && read < bufSize {
|
||||||
|
var n int
|
||||||
|
n, err = mb.media.Read(mb.chunk[read:])
|
||||||
|
read += n
|
||||||
|
}
|
||||||
|
mb.chunk = mb.chunk[:read]
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances to the next chunk, which will be returned by the next call to Chunk.
|
||||||
|
// Calls to Next without a corresponding prior call to Chunk will have no effect.
|
||||||
|
func (mb *MediaBuffer) Next() {
|
||||||
|
mb.off += int64(len(mb.chunk))
|
||||||
|
mb.chunk = mb.chunk[0:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type readerTyper struct {
|
||||||
|
io.Reader
|
||||||
|
googleapi.ContentTyper
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReaderAtToReader adapts a ReaderAt to be used as a Reader.
|
||||||
|
// If ra implements googleapi.ContentTyper, then the returned reader
|
||||||
|
// will also implement googleapi.ContentTyper, delegating to ra.
|
||||||
|
func ReaderAtToReader(ra io.ReaderAt, size int64) io.Reader {
|
||||||
|
r := io.NewSectionReader(ra, 0, size)
|
||||||
|
if typer, ok := ra.(googleapi.ContentTyper); ok {
|
||||||
|
return readerTyper{r, typer}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gensupport is an internal implementation detail used by code
|
||||||
|
// generated by the google-api-go-generator tool.
|
||||||
|
//
|
||||||
|
// This package may be modified at any time without regard for backwards
|
||||||
|
// compatibility. It should not be used directly by API users.
|
||||||
|
package gensupport
|
|
@ -0,0 +1,184 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalJSON returns a JSON encoding of schema containing only selected fields.
|
||||||
|
// A field is selected if any of the following is true:
|
||||||
|
// * it has a non-empty value
|
||||||
|
// * its field name is present in forceSendFields and it is not a nil pointer or nil interface
|
||||||
|
// * its field name is present in nullFields.
|
||||||
|
// The JSON key for each selected field is taken from the field's json: struct tag.
|
||||||
|
func MarshalJSON(schema interface{}, forceSendFields, nullFields []string) ([]byte, error) {
|
||||||
|
if len(forceSendFields) == 0 && len(nullFields) == 0 {
|
||||||
|
return json.Marshal(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
mustInclude := make(map[string]struct{})
|
||||||
|
for _, f := range forceSendFields {
|
||||||
|
mustInclude[f] = struct{}{}
|
||||||
|
}
|
||||||
|
useNull := make(map[string]struct{})
|
||||||
|
for _, f := range nullFields {
|
||||||
|
useNull[f] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMap, err := schemaToMap(schema, mustInclude, useNull)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(dataMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func schemaToMap(schema interface{}, mustInclude, useNull map[string]struct{}) (map[string]interface{}, error) {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
s := reflect.ValueOf(schema)
|
||||||
|
st := s.Type()
|
||||||
|
|
||||||
|
for i := 0; i < s.NumField(); i++ {
|
||||||
|
jsonTag := st.Field(i).Tag.Get("json")
|
||||||
|
if jsonTag == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tag, err := parseJSONTag(jsonTag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tag.ignore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v := s.Field(i)
|
||||||
|
f := st.Field(i)
|
||||||
|
|
||||||
|
if _, ok := useNull[f.Name]; ok {
|
||||||
|
if !isEmptyValue(v) {
|
||||||
|
return nil, fmt.Errorf("field %q in NullFields has non-empty value", f.Name)
|
||||||
|
}
|
||||||
|
m[tag.apiName] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !includeField(v, f, mustInclude) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// nil maps are treated as empty maps.
|
||||||
|
if f.Type.Kind() == reflect.Map && v.IsNil() {
|
||||||
|
m[tag.apiName] = map[string]string{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// nil slices are treated as empty slices.
|
||||||
|
if f.Type.Kind() == reflect.Slice && v.IsNil() {
|
||||||
|
m[tag.apiName] = []bool{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag.stringFormat {
|
||||||
|
m[tag.apiName] = formatAsString(v, f.Type.Kind())
|
||||||
|
} else {
|
||||||
|
m[tag.apiName] = v.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatAsString returns a string representation of v, dereferencing it first if possible.
|
||||||
|
func formatAsString(v reflect.Value, kind reflect.Kind) string {
|
||||||
|
if kind == reflect.Ptr && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v", v.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonTag represents a restricted version of the struct tag format used by encoding/json.
|
||||||
|
// It is used to describe the JSON encoding of fields in a Schema struct.
|
||||||
|
type jsonTag struct {
|
||||||
|
apiName string
|
||||||
|
stringFormat bool
|
||||||
|
ignore bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseJSONTag parses a restricted version of the struct tag format used by encoding/json.
|
||||||
|
// The format of the tag must match that generated by the Schema.writeSchemaStruct method
|
||||||
|
// in the api generator.
|
||||||
|
func parseJSONTag(val string) (jsonTag, error) {
|
||||||
|
if val == "-" {
|
||||||
|
return jsonTag{ignore: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tag jsonTag
|
||||||
|
|
||||||
|
i := strings.Index(val, ",")
|
||||||
|
if i == -1 || val[:i] == "" {
|
||||||
|
return tag, fmt.Errorf("malformed json tag: %s", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = jsonTag{
|
||||||
|
apiName: val[:i],
|
||||||
|
}
|
||||||
|
|
||||||
|
switch val[i+1:] {
|
||||||
|
case "omitempty":
|
||||||
|
case "omitempty,string":
|
||||||
|
tag.stringFormat = true
|
||||||
|
default:
|
||||||
|
return tag, fmt.Errorf("malformed json tag: %s", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reports whether the struct field "f" with value "v" should be included in JSON output.
|
||||||
|
func includeField(v reflect.Value, f reflect.StructField, mustInclude map[string]struct{}) bool {
|
||||||
|
// The regular JSON encoding of a nil pointer is "null", which means "delete this field".
|
||||||
|
// Therefore, we could enable field deletion by honoring pointer fields' presence in the mustInclude set.
|
||||||
|
// However, many fields are not pointers, so there would be no way to delete these fields.
|
||||||
|
// Rather than partially supporting field deletion, we ignore mustInclude for nil pointer fields.
|
||||||
|
// Deletion will be handled by a separate mechanism.
|
||||||
|
if f.Type.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "any" type is represented as an interface{}. If this interface
|
||||||
|
// is nil, there is no reasonable representation to send. We ignore
|
||||||
|
// these fields, for the same reasons as given above for pointers.
|
||||||
|
if f.Type.Kind() == reflect.Interface && v.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := mustInclude[f.Name]
|
||||||
|
return ok || !isEmptyValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmptyValue reports whether v is the empty value for its type. This
|
||||||
|
// implementation is based on that of the encoding/json package, but its
|
||||||
|
// correctness does not depend on it being identical. What's important is that
|
||||||
|
// this function return false in situations where v should not be sent as part
|
||||||
|
// of a PATCH operation.
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sniffBuffSize = 512
|
||||||
|
|
||||||
|
func newContentSniffer(r io.Reader) *contentSniffer {
|
||||||
|
return &contentSniffer{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentSniffer wraps a Reader, and reports the content type determined by sniffing up to 512 bytes from the Reader.
|
||||||
|
type contentSniffer struct {
|
||||||
|
r io.Reader
|
||||||
|
start []byte // buffer for the sniffed bytes.
|
||||||
|
err error // set to any error encountered while reading bytes to be sniffed.
|
||||||
|
|
||||||
|
ctype string // set on first sniff.
|
||||||
|
sniffed bool // set to true on first sniff.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *contentSniffer) Read(p []byte) (n int, err error) {
|
||||||
|
// Ensure that the content type is sniffed before any data is consumed from Reader.
|
||||||
|
_, _ = cs.ContentType()
|
||||||
|
|
||||||
|
if len(cs.start) > 0 {
|
||||||
|
n := copy(p, cs.start)
|
||||||
|
cs.start = cs.start[n:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may have read some bytes into start while sniffing, even if the read ended in an error.
|
||||||
|
// We should first return those bytes, then the error.
|
||||||
|
if cs.err != nil {
|
||||||
|
return 0, cs.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have handled all bytes that were buffered while sniffing. Now just delegate to the underlying reader.
|
||||||
|
return cs.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns the sniffed content type, and whether the content type was succesfully sniffed.
|
||||||
|
func (cs *contentSniffer) ContentType() (string, bool) {
|
||||||
|
if cs.sniffed {
|
||||||
|
return cs.ctype, cs.ctype != ""
|
||||||
|
}
|
||||||
|
cs.sniffed = true
|
||||||
|
// If ReadAll hits EOF, it returns err==nil.
|
||||||
|
cs.start, cs.err = ioutil.ReadAll(io.LimitReader(cs.r, sniffBuffSize))
|
||||||
|
|
||||||
|
// Don't try to detect the content type based on possibly incomplete data.
|
||||||
|
if cs.err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.ctype = http.DetectContentType(cs.start)
|
||||||
|
return cs.ctype, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetermineContentType determines the content type of the supplied reader.
|
||||||
|
// If the content type is already known, it can be specified via ctype.
|
||||||
|
// Otherwise, the content of media will be sniffed to determine the content type.
|
||||||
|
// If media implements googleapi.ContentTyper (deprecated), this will be used
|
||||||
|
// instead of sniffing the content.
|
||||||
|
// After calling DetectContentType the caller must not perform further reads on
|
||||||
|
// media, but rather read from the Reader that is returned.
|
||||||
|
func DetermineContentType(media io.Reader, ctype string) (io.Reader, string) {
|
||||||
|
// Note: callers could avoid calling DetectContentType if ctype != "",
|
||||||
|
// but doing the check inside this function reduces the amount of
|
||||||
|
// generated code.
|
||||||
|
if ctype != "" {
|
||||||
|
return media, ctype
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backwards compatability, allow clients to set content
|
||||||
|
// type by providing a ContentTyper for media.
|
||||||
|
if typer, ok := media.(googleapi.ContentTyper); ok {
|
||||||
|
return media, typer.ContentType()
|
||||||
|
}
|
||||||
|
|
||||||
|
sniffer := newContentSniffer(media)
|
||||||
|
if ctype, ok := sniffer.ContentType(); ok {
|
||||||
|
return sniffer, ctype
|
||||||
|
}
|
||||||
|
// If content type could not be sniffed, reads from sniffer will eventually fail with an error.
|
||||||
|
return sniffer, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeReader struct {
|
||||||
|
io.Reader
|
||||||
|
typ string
|
||||||
|
}
|
||||||
|
|
||||||
|
// multipartReader combines the contents of multiple readers to creat a multipart/related HTTP body.
|
||||||
|
// Close must be called if reads from the multipartReader are abandoned before reaching EOF.
|
||||||
|
type multipartReader struct {
|
||||||
|
pr *io.PipeReader
|
||||||
|
pipeOpen bool
|
||||||
|
ctype string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultipartReader(parts []typeReader) *multipartReader {
|
||||||
|
mp := &multipartReader{pipeOpen: true}
|
||||||
|
var pw *io.PipeWriter
|
||||||
|
mp.pr, pw = io.Pipe()
|
||||||
|
mpw := multipart.NewWriter(pw)
|
||||||
|
mp.ctype = "multipart/related; boundary=" + mpw.Boundary()
|
||||||
|
go func() {
|
||||||
|
for _, part := range parts {
|
||||||
|
w, err := mpw.CreatePart(typeHeader(part.typ))
|
||||||
|
if err != nil {
|
||||||
|
mpw.Close()
|
||||||
|
pw.CloseWithError(fmt.Errorf("googleapi: CreatePart failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, part.Reader)
|
||||||
|
if err != nil {
|
||||||
|
mpw.Close()
|
||||||
|
pw.CloseWithError(fmt.Errorf("googleapi: Copy failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw.Close()
|
||||||
|
pw.Close()
|
||||||
|
}()
|
||||||
|
return mp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *multipartReader) Read(data []byte) (n int, err error) {
|
||||||
|
return mp.pr.Read(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *multipartReader) Close() error {
|
||||||
|
if !mp.pipeOpen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mp.pipeOpen = false
|
||||||
|
return mp.pr.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombineBodyMedia combines a json body with media content to create a multipart/related HTTP body.
|
||||||
|
// It returns a ReadCloser containing the combined body, and the overall "multipart/related" content type, with random boundary.
|
||||||
|
//
|
||||||
|
// The caller must call Close on the returned ReadCloser if reads are abandoned before reaching EOF.
|
||||||
|
func CombineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType string) (io.ReadCloser, string) {
|
||||||
|
mp := newMultipartReader([]typeReader{
|
||||||
|
{body, bodyContentType},
|
||||||
|
{media, mediaContentType},
|
||||||
|
})
|
||||||
|
return mp, mp.ctype
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeHeader(contentType string) textproto.MIMEHeader {
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
if contentType != "" {
|
||||||
|
h.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareUpload determines whether the data in the supplied reader should be
|
||||||
|
// uploaded in a single request, or in sequential chunks.
|
||||||
|
// chunkSize is the size of the chunk that media should be split into.
|
||||||
|
// If chunkSize is non-zero and the contents of media do not fit in a single
|
||||||
|
// chunk (or there is an error reading media), then media will be returned as a
|
||||||
|
// MediaBuffer. Otherwise, media will be returned as a Reader.
|
||||||
|
//
|
||||||
|
// After PrepareUpload has been called, media should no longer be used: the
|
||||||
|
// media content should be accessed via one of the return values.
|
||||||
|
func PrepareUpload(media io.Reader, chunkSize int) (io.Reader, *MediaBuffer) {
|
||||||
|
if chunkSize == 0 { // do not chunk
|
||||||
|
return media, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mb := NewMediaBuffer(media, chunkSize)
|
||||||
|
rdr, _, _, err := mb.Chunk()
|
||||||
|
|
||||||
|
if err == io.EOF { // we can upload this in a single request
|
||||||
|
return rdr, nil
|
||||||
|
}
|
||||||
|
// err might be a non-EOF error. If it is, the next call to mb.Chunk will
|
||||||
|
// return the same error. Returning a MediaBuffer ensures that this error
|
||||||
|
// will be handled at some point.
|
||||||
|
|
||||||
|
return nil, mb
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URLParams is a simplified replacement for url.Values
|
||||||
|
// that safely builds up URL parameters for encoding.
|
||||||
|
type URLParams map[string][]string
|
||||||
|
|
||||||
|
// Get returns the first value for the given key, or "".
|
||||||
|
func (u URLParams) Get(key string) string {
|
||||||
|
vs := u[key]
|
||||||
|
if len(vs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return vs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the key to value.
|
||||||
|
// It replaces any existing values.
|
||||||
|
func (u URLParams) Set(key, value string) {
|
||||||
|
u[key] = []string{value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMulti sets the key to an array of values.
|
||||||
|
// It replaces any existing values.
|
||||||
|
// Note that values must not be modified after calling SetMulti
|
||||||
|
// so the caller is responsible for making a copy if necessary.
|
||||||
|
func (u URLParams) SetMulti(key string, values []string) {
|
||||||
|
u[key] = values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the values into ``URL encoded'' form
|
||||||
|
// ("bar=baz&foo=quux") sorted by key.
|
||||||
|
func (u URLParams) Encode() string {
|
||||||
|
return url.Values(u).Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetOptions(u URLParams, opts ...googleapi.CallOption) {
|
||||||
|
for _, o := range opts {
|
||||||
|
u.Set(o.Get())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// statusTooManyRequests is returned by the storage API if the
|
||||||
|
// per-project limits have been temporarily exceeded. The request
|
||||||
|
// should be retried.
|
||||||
|
// https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
|
||||||
|
statusTooManyRequests = 429
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResumableUpload is used by the generated APIs to provide resumable uploads.
|
||||||
|
// It is not used by developers directly.
|
||||||
|
type ResumableUpload struct {
|
||||||
|
Client *http.Client
|
||||||
|
// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable".
|
||||||
|
URI string
|
||||||
|
UserAgent string // User-Agent for header of the request
|
||||||
|
// Media is the object being uploaded.
|
||||||
|
Media *MediaBuffer
|
||||||
|
// MediaType defines the media type, e.g. "image/jpeg".
|
||||||
|
MediaType string
|
||||||
|
|
||||||
|
mu sync.Mutex // guards progress
|
||||||
|
progress int64 // number of bytes uploaded so far
|
||||||
|
|
||||||
|
// Callback is an optional function that will be periodically called with the cumulative number of bytes uploaded.
|
||||||
|
Callback func(int64)
|
||||||
|
|
||||||
|
// If not specified, a default exponential backoff strategy will be used.
|
||||||
|
Backoff BackoffStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress returns the number of bytes uploaded at this point.
|
||||||
|
func (rx *ResumableUpload) Progress() int64 {
|
||||||
|
rx.mu.Lock()
|
||||||
|
defer rx.mu.Unlock()
|
||||||
|
return rx.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
// doUploadRequest performs a single HTTP request to upload data.
|
||||||
|
// off specifies the offset in rx.Media from which data is drawn.
|
||||||
|
// size is the number of bytes in data.
|
||||||
|
// final specifies whether data is the final chunk to be uploaded.
|
||||||
|
func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader, off, size int64, final bool) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest("POST", rx.URI, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ContentLength = size
|
||||||
|
var contentRange string
|
||||||
|
if final {
|
||||||
|
if size == 0 {
|
||||||
|
contentRange = fmt.Sprintf("bytes */%v", off)
|
||||||
|
} else {
|
||||||
|
contentRange = fmt.Sprintf("bytes %v-%v/%v", off, off+size-1, off+size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contentRange = fmt.Sprintf("bytes %v-%v/*", off, off+size-1)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Range", contentRange)
|
||||||
|
req.Header.Set("Content-Type", rx.MediaType)
|
||||||
|
req.Header.Set("User-Agent", rx.UserAgent)
|
||||||
|
|
||||||
|
// Google's upload endpoint uses status code 308 for a
|
||||||
|
// different purpose than the "308 Permanent Redirect"
|
||||||
|
// since-standardized in RFC 7238. Because of the conflict in
|
||||||
|
// semantics, Google added this new request header which
|
||||||
|
// causes it to not use "308" and instead reply with 200 OK
|
||||||
|
// and sets the upload-specific "X-HTTP-Status-Code-Override:
|
||||||
|
// 308" response header.
|
||||||
|
req.Header.Set("X-GUploader-No-308", "yes")
|
||||||
|
|
||||||
|
return SendRequest(ctx, rx.Client, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusResumeIncomplete(resp *http.Response) bool {
|
||||||
|
// This is how the server signals "status resume incomplete"
|
||||||
|
// when X-GUploader-No-308 is set to "yes":
|
||||||
|
return resp != nil && resp.Header.Get("X-Http-Status-Code-Override") == "308"
|
||||||
|
}
|
||||||
|
|
||||||
|
// reportProgress calls a user-supplied callback to report upload progress.
|
||||||
|
// If old==updated, the callback is not called.
|
||||||
|
func (rx *ResumableUpload) reportProgress(old, updated int64) {
|
||||||
|
if updated-old == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rx.mu.Lock()
|
||||||
|
rx.progress = updated
|
||||||
|
rx.mu.Unlock()
|
||||||
|
if rx.Callback != nil {
|
||||||
|
rx.Callback(updated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transferChunk performs a single HTTP request to upload a single chunk from rx.Media.
|
||||||
|
func (rx *ResumableUpload) transferChunk(ctx context.Context) (*http.Response, error) {
|
||||||
|
chunk, off, size, err := rx.Media.Chunk()
|
||||||
|
|
||||||
|
done := err == io.EOF
|
||||||
|
if !done && err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := rx.doUploadRequest(ctx, chunk, off, int64(size), done)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We sent "X-GUploader-No-308: yes" (see comment elsewhere in
|
||||||
|
// this file), so we don't expect to get a 308.
|
||||||
|
if res.StatusCode == 308 {
|
||||||
|
return nil, errors.New("unexpected 308 response status code")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusOK {
|
||||||
|
rx.reportProgress(off, off+int64(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusResumeIncomplete(res) {
|
||||||
|
rx.Media.Next()
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextDone(ctx context.Context) bool {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload starts the process of a resumable upload with a cancellable context.
|
||||||
|
// It retries using the provided back off strategy until cancelled or the
|
||||||
|
// strategy indicates to stop retrying.
|
||||||
|
// It is called from the auto-generated API code and is not visible to the user.
|
||||||
|
// Before sending an HTTP request, Upload calls any registered hook functions,
|
||||||
|
// and calls the returned functions after the request returns (see send.go).
|
||||||
|
// rx is private to the auto-generated API code.
|
||||||
|
// Exactly one of resp or err will be nil. If resp is non-nil, the caller must call resp.Body.Close.
|
||||||
|
func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err error) {
|
||||||
|
var pause time.Duration
|
||||||
|
backoff := rx.Backoff
|
||||||
|
if backoff == nil {
|
||||||
|
backoff = DefaultBackoffStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Ensure that we return in the case of cancelled context, even if pause is 0.
|
||||||
|
if contextDone(ctx) {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-time.After(pause):
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = rx.transferChunk(ctx)
|
||||||
|
|
||||||
|
var status int
|
||||||
|
if resp != nil {
|
||||||
|
status = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should retry the request.
|
||||||
|
if shouldRetry(status, err) {
|
||||||
|
var retry bool
|
||||||
|
pause, retry = backoff.Pause()
|
||||||
|
if retry {
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the chunk was uploaded successfully, but there's still
|
||||||
|
// more to go, upload the next chunk without any delay.
|
||||||
|
if statusResumeIncomplete(resp) {
|
||||||
|
pause = 0
|
||||||
|
backoff.Reset()
|
||||||
|
resp.Body.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's possible for err and resp to both be non-nil here, but we expose a simpler
|
||||||
|
// contract to our callers: exactly one of resp and err will be non-nil. This means
|
||||||
|
// that any response body must be closed here before returning a non-nil error.
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Retry invokes the given function, retrying it multiple times if the connection failed or
|
||||||
|
// the HTTP status response indicates the request should be attempted again. ctx may be nil.
|
||||||
|
func Retry(ctx context.Context, f func() (*http.Response, error), backoff BackoffStrategy) (*http.Response, error) {
|
||||||
|
for {
|
||||||
|
resp, err := f()
|
||||||
|
|
||||||
|
var status int
|
||||||
|
if resp != nil {
|
||||||
|
status = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if we shouldn't retry.
|
||||||
|
pause, retry := backoff.Pause()
|
||||||
|
if !shouldRetry(status, err) || !retry {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the response body is closed, if any.
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause, but still listen to ctx.Done if context is not nil.
|
||||||
|
var done <-chan struct{}
|
||||||
|
if ctx != nil {
|
||||||
|
done = ctx.Done()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-time.After(pause):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBackoffStrategy returns a default strategy to use for retrying failed upload requests.
|
||||||
|
func DefaultBackoffStrategy() BackoffStrategy {
|
||||||
|
return &ExponentialBackoff{
|
||||||
|
Base: 250 * time.Millisecond,
|
||||||
|
Max: 16 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRetry returns true if the HTTP response / error indicates that the
|
||||||
|
// request should be attempted again.
|
||||||
|
func shouldRetry(status int, err error) bool {
|
||||||
|
if 500 <= status && status <= 599 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if status == statusTooManyRequests {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err, ok := err.(net.Error); ok {
|
||||||
|
return err.Temporary()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gensupport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/context/ctxhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hook is the type of a function that is called once before each HTTP request
|
||||||
|
// that is sent by a generated API. It returns a function that is called after
|
||||||
|
// the request returns.
|
||||||
|
// Hooks are not called if the context is nil.
|
||||||
|
type Hook func(ctx context.Context, req *http.Request) func(resp *http.Response)
|
||||||
|
|
||||||
|
var hooks []Hook
|
||||||
|
|
||||||
|
// RegisterHook registers a Hook to be called before each HTTP request by a
|
||||||
|
// generated API. Hooks are called in the order they are registered. Each
|
||||||
|
// hook can return a function; if it is non-nil, it is called after the HTTP
|
||||||
|
// request returns. These functions are called in the reverse order.
|
||||||
|
// RegisterHook should not be called concurrently with itself or SendRequest.
|
||||||
|
func RegisterHook(h Hook) {
|
||||||
|
hooks = append(hooks, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRequest sends a single HTTP request using the given client.
|
||||||
|
// If ctx is non-nil, it calls all hooks, then sends the request with
|
||||||
|
// ctxhttp.Do, then calls any functions returned by the hooks in reverse order.
|
||||||
|
func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
// Call hooks in order of registration, store returned funcs.
|
||||||
|
post := make([]func(resp *http.Response), len(hooks))
|
||||||
|
for i, h := range hooks {
|
||||||
|
fn := h(ctx, req)
|
||||||
|
post[i] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request.
|
||||||
|
resp, err := ctxhttp.Do(ctx, client, req)
|
||||||
|
|
||||||
|
// Call returned funcs in reverse order.
|
||||||
|
for i := len(post) - 1; i >= 0; i-- {
|
||||||
|
if fn := post[i]; fn != nil {
|
||||||
|
fn(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
|
@ -0,0 +1,406 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package googleapi contains the common code shared by all Google API
|
||||||
|
// libraries.
|
||||||
|
package googleapi // import "google.golang.org/api/googleapi"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/api/googleapi/internal/uritemplates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContentTyper is an interface for Readers which know (or would like
|
||||||
|
// to override) their Content-Type. If a media body doesn't implement
|
||||||
|
// ContentTyper, the type is sniffed from the content using
|
||||||
|
// http.DetectContentType.
|
||||||
|
type ContentTyper interface {
|
||||||
|
ContentType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SizeReaderAt is a ReaderAt with a Size method.
|
||||||
|
// An io.SectionReader implements SizeReaderAt.
|
||||||
|
type SizeReaderAt interface {
|
||||||
|
io.ReaderAt
|
||||||
|
Size() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerResponse is embedded in each Do response and
|
||||||
|
// provides the HTTP status code and header sent by the server.
|
||||||
|
type ServerResponse struct {
|
||||||
|
// HTTPStatusCode is the server's response status code.
|
||||||
|
// When using a resource method's Do call, this will always be in the 2xx range.
|
||||||
|
HTTPStatusCode int
|
||||||
|
// Header contains the response header fields from the server.
|
||||||
|
Header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = "0.5"
|
||||||
|
|
||||||
|
// UserAgent is the header string used to identify this package.
|
||||||
|
UserAgent = "google-api-go-client/" + Version
|
||||||
|
|
||||||
|
// The default chunk size to use for resumable uploads if not specified by the user.
|
||||||
|
DefaultUploadChunkSize = 8 * 1024 * 1024
|
||||||
|
|
||||||
|
// The minimum chunk size that can be used for resumable uploads. All
|
||||||
|
// user-specified chunk sizes must be multiple of this value.
|
||||||
|
MinUploadChunkSize = 256 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error contains an error response from the server.
|
||||||
|
type Error struct {
|
||||||
|
// Code is the HTTP response status code and will always be populated.
|
||||||
|
Code int `json:"code"`
|
||||||
|
// Message is the server response message and is only populated when
|
||||||
|
// explicitly referenced by the JSON server response.
|
||||||
|
Message string `json:"message"`
|
||||||
|
// Body is the raw response returned by the server.
|
||||||
|
// It is often but not always JSON, depending on how the request fails.
|
||||||
|
Body string
|
||||||
|
// Header contains the response header fields from the server.
|
||||||
|
Header http.Header
|
||||||
|
|
||||||
|
Errors []ErrorItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorItem is a detailed error code & message from the Google API frontend.
|
||||||
|
type ErrorItem struct {
|
||||||
|
// Reason is the typed error code. For example: "some_example".
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
// Message is the human-readable description of the error.
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
if len(e.Errors) == 0 && e.Message == "" {
|
||||||
|
return fmt.Sprintf("googleapi: got HTTP response code %d with body: %v", e.Code, e.Body)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, "googleapi: Error %d: ", e.Code)
|
||||||
|
if e.Message != "" {
|
||||||
|
fmt.Fprintf(&buf, "%s", e.Message)
|
||||||
|
}
|
||||||
|
if len(e.Errors) == 0 {
|
||||||
|
return strings.TrimSpace(buf.String())
|
||||||
|
}
|
||||||
|
if len(e.Errors) == 1 && e.Errors[0].Message == e.Message {
|
||||||
|
fmt.Fprintf(&buf, ", %s", e.Errors[0].Reason)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
fmt.Fprintln(&buf, "\nMore details:")
|
||||||
|
for _, v := range e.Errors {
|
||||||
|
fmt.Fprintf(&buf, "Reason: %s, Message: %s\n", v.Reason, v.Message)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorReply struct {
|
||||||
|
Error *Error `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResponse returns an error (of type *Error) if the response
|
||||||
|
// status code is not 2xx.
|
||||||
|
func CheckResponse(res *http.Response) error {
|
||||||
|
if res.StatusCode >= 200 && res.StatusCode <= 299 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slurp, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err == nil {
|
||||||
|
jerr := new(errorReply)
|
||||||
|
err = json.Unmarshal(slurp, jerr)
|
||||||
|
if err == nil && jerr.Error != nil {
|
||||||
|
if jerr.Error.Code == 0 {
|
||||||
|
jerr.Error.Code = res.StatusCode
|
||||||
|
}
|
||||||
|
jerr.Error.Body = string(slurp)
|
||||||
|
return jerr.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
Code: res.StatusCode,
|
||||||
|
Body: string(slurp),
|
||||||
|
Header: res.Header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotModified reports whether err is the result of the
|
||||||
|
// server replying with http.StatusNotModified.
|
||||||
|
// Such error values are sometimes returned by "Do" methods
|
||||||
|
// on calls when If-None-Match is used.
|
||||||
|
func IsNotModified(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ae, ok := err.(*Error)
|
||||||
|
return ok && ae.Code == http.StatusNotModified
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMediaResponse returns an error (of type *Error) if the response
|
||||||
|
// status code is not 2xx. Unlike CheckResponse it does not assume the
|
||||||
|
// body is a JSON error document.
|
||||||
|
// It is the caller's responsibility to close res.Body.
|
||||||
|
func CheckMediaResponse(res *http.Response) error {
|
||||||
|
if res.StatusCode >= 200 && res.StatusCode <= 299 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slurp, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||||
|
return &Error{
|
||||||
|
Code: res.StatusCode,
|
||||||
|
Body: string(slurp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarshalStyle bool
|
||||||
|
|
||||||
|
var WithDataWrapper = MarshalStyle(true)
|
||||||
|
var WithoutDataWrapper = MarshalStyle(false)
|
||||||
|
|
||||||
|
func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if wrap {
|
||||||
|
buf.Write([]byte(`{"data": `))
|
||||||
|
}
|
||||||
|
err := json.NewEncoder(buf).Encode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if wrap {
|
||||||
|
buf.Write([]byte(`}`))
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// endingWithErrorReader from r until it returns an error. If the
|
||||||
|
// final error from r is io.EOF and e is non-nil, e is used instead.
|
||||||
|
type endingWithErrorReader struct {
|
||||||
|
r io.Reader
|
||||||
|
e error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er endingWithErrorReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = er.r.Read(p)
|
||||||
|
if err == io.EOF && er.e != nil {
|
||||||
|
err = er.e
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// countingWriter counts the number of bytes it receives to write, but
|
||||||
|
// discards them.
|
||||||
|
type countingWriter struct {
|
||||||
|
n *int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w countingWriter) Write(p []byte) (int, error) {
|
||||||
|
*w.n += int64(len(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressUpdater is a function that is called upon every progress update of a resumable upload.
|
||||||
|
// This is the only part of a resumable upload (from googleapi) that is usable by the developer.
|
||||||
|
// The remaining usable pieces of resumable uploads is exposed in each auto-generated API.
|
||||||
|
type ProgressUpdater func(current, total int64)
|
||||||
|
|
||||||
|
type MediaOption interface {
|
||||||
|
setOptions(o *MediaOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentTypeOption string
|
||||||
|
|
||||||
|
func (ct contentTypeOption) setOptions(o *MediaOptions) {
|
||||||
|
o.ContentType = string(ct)
|
||||||
|
if o.ContentType == "" {
|
||||||
|
o.ForceEmptyContentType = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns a MediaOption which sets the Content-Type header for media uploads.
|
||||||
|
// If ctype is empty, the Content-Type header will be omitted.
|
||||||
|
func ContentType(ctype string) MediaOption {
|
||||||
|
return contentTypeOption(ctype)
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkSizeOption int
|
||||||
|
|
||||||
|
func (cs chunkSizeOption) setOptions(o *MediaOptions) {
|
||||||
|
size := int(cs)
|
||||||
|
if size%MinUploadChunkSize != 0 {
|
||||||
|
size += MinUploadChunkSize - (size % MinUploadChunkSize)
|
||||||
|
}
|
||||||
|
o.ChunkSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChunkSize returns a MediaOption which sets the chunk size for media uploads.
|
||||||
|
// size will be rounded up to the nearest multiple of 256K.
|
||||||
|
// Media which contains fewer than size bytes will be uploaded in a single request.
|
||||||
|
// Media which contains size bytes or more will be uploaded in separate chunks.
|
||||||
|
// If size is zero, media will be uploaded in a single request.
|
||||||
|
func ChunkSize(size int) MediaOption {
|
||||||
|
return chunkSizeOption(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MediaOptions stores options for customizing media upload. It is not used by developers directly.
|
||||||
|
type MediaOptions struct {
|
||||||
|
ContentType string
|
||||||
|
ForceEmptyContentType bool
|
||||||
|
|
||||||
|
ChunkSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessMediaOptions stores options from opts in a MediaOptions.
|
||||||
|
// It is not used by developers directly.
|
||||||
|
func ProcessMediaOptions(opts []MediaOption) *MediaOptions {
|
||||||
|
mo := &MediaOptions{ChunkSize: DefaultUploadChunkSize}
|
||||||
|
for _, o := range opts {
|
||||||
|
o.setOptions(mo)
|
||||||
|
}
|
||||||
|
return mo
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveRelative(basestr, relstr string) string {
|
||||||
|
u, _ := url.Parse(basestr)
|
||||||
|
rel, _ := url.Parse(relstr)
|
||||||
|
u = u.ResolveReference(rel)
|
||||||
|
us := u.String()
|
||||||
|
us = strings.Replace(us, "%7B", "{", -1)
|
||||||
|
us = strings.Replace(us, "%7D", "}", -1)
|
||||||
|
return us
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand subsitutes any {encoded} strings in the URL passed in using
|
||||||
|
// the map supplied.
|
||||||
|
//
|
||||||
|
// This calls SetOpaque to avoid encoding of the parameters in the URL path.
|
||||||
|
func Expand(u *url.URL, expansions map[string]string) {
|
||||||
|
escaped, unescaped, err := uritemplates.Expand(u.Path, expansions)
|
||||||
|
if err == nil {
|
||||||
|
u.Path = unescaped
|
||||||
|
u.RawPath = escaped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseBody is used to close res.Body.
|
||||||
|
// Prior to calling Close, it also tries to Read a small amount to see an EOF.
|
||||||
|
// Not seeing an EOF can prevent HTTP Transports from reusing connections.
|
||||||
|
func CloseBody(res *http.Response) {
|
||||||
|
if res == nil || res.Body == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Justification for 3 byte reads: two for up to "\r\n" after
|
||||||
|
// a JSON/XML document, and then 1 to see EOF if we haven't yet.
|
||||||
|
// TODO(bradfitz): detect Go 1.3+ and skip these reads.
|
||||||
|
// See https://codereview.appspot.com/58240043
|
||||||
|
// and https://codereview.appspot.com/49570044
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
_, err := res.Body.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariantType returns the type name of the given variant.
|
||||||
|
// If the map doesn't contain the named key or the value is not a []interface{}, "" is returned.
|
||||||
|
// This is used to support "variant" APIs that can return one of a number of different types.
|
||||||
|
func VariantType(t map[string]interface{}) string {
|
||||||
|
s, _ := t["type"].(string)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertVariant uses the JSON encoder/decoder to fill in the struct 'dst' with the fields found in variant 'v'.
|
||||||
|
// This is used to support "variant" APIs that can return one of a number of different types.
|
||||||
|
// It reports whether the conversion was successful.
|
||||||
|
func ConvertVariant(v map[string]interface{}, dst interface{}) bool {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := json.NewEncoder(&buf).Encode(v)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return json.Unmarshal(buf.Bytes(), dst) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Field names a field to be retrieved with a partial response.
|
||||||
|
// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||||
|
//
|
||||||
|
// Partial responses can dramatically reduce the amount of data that must be sent to your application.
|
||||||
|
// In order to request partial responses, you can specify the full list of fields
|
||||||
|
// that your application needs by adding the Fields option to your request.
|
||||||
|
//
|
||||||
|
// Field strings use camelCase with leading lower-case characters to identify fields within the response.
|
||||||
|
//
|
||||||
|
// For example, if your response has a "NextPageToken" and a slice of "Items" with "Id" fields,
|
||||||
|
// you could request just those fields like this:
|
||||||
|
//
|
||||||
|
// svc.Events.List().Fields("nextPageToken", "items/id").Do()
|
||||||
|
//
|
||||||
|
// or if you were also interested in each Item's "Updated" field, you can combine them like this:
|
||||||
|
//
|
||||||
|
// svc.Events.List().Fields("nextPageToken", "items(id,updated)").Do()
|
||||||
|
//
|
||||||
|
// More information about field formatting can be found here:
|
||||||
|
// https://developers.google.com/+/api/#fields-syntax
|
||||||
|
//
|
||||||
|
// Another way to find field names is through the Google API explorer:
|
||||||
|
// https://developers.google.com/apis-explorer/#p/
|
||||||
|
type Field string
|
||||||
|
|
||||||
|
// CombineFields combines fields into a single string.
|
||||||
|
func CombineFields(s []Field) string {
|
||||||
|
r := make([]string, len(s))
|
||||||
|
for i, v := range s {
|
||||||
|
r[i] = string(v)
|
||||||
|
}
|
||||||
|
return strings.Join(r, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CallOption is an optional argument to an API call.
|
||||||
|
// It should be treated as an opaque value by users of Google APIs.
|
||||||
|
//
|
||||||
|
// A CallOption is something that configures an API call in a way that is
|
||||||
|
// not specific to that API; for instance, controlling the quota user for
|
||||||
|
// an API call is common across many APIs, and is thus a CallOption.
|
||||||
|
type CallOption interface {
|
||||||
|
Get() (key, value string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuotaUser returns a CallOption that will set the quota user for a call.
|
||||||
|
// The quota user can be used by server-side applications to control accounting.
|
||||||
|
// It can be an arbitrary string up to 40 characters, and will override UserIP
|
||||||
|
// if both are provided.
|
||||||
|
func QuotaUser(u string) CallOption { return quotaUser(u) }
|
||||||
|
|
||||||
|
type quotaUser string
|
||||||
|
|
||||||
|
func (q quotaUser) Get() (string, string) { return "quotaUser", string(q) }
|
||||||
|
|
||||||
|
// UserIP returns a CallOption that will set the "userIp" parameter of a call.
|
||||||
|
// This should be the IP address of the originating request.
|
||||||
|
func UserIP(ip string) CallOption { return userIP(ip) }
|
||||||
|
|
||||||
|
type userIP string
|
||||||
|
|
||||||
|
func (i userIP) Get() (string, string) { return "userIp", string(i) }
|
||||||
|
|
||||||
|
// Trace returns a CallOption that enables diagnostic tracing for a call.
|
||||||
|
// traceToken is an ID supplied by Google support.
|
||||||
|
func Trace(traceToken string) CallOption { return traceTok(traceToken) }
|
||||||
|
|
||||||
|
type traceTok string
|
||||||
|
|
||||||
|
func (t traceTok) Get() (string, string) { return "trace", "token:" + string(t) }
|
||||||
|
|
||||||
|
// TODO: Fields too
|
18
vendor/google.golang.org/api/googleapi/internal/uritemplates/LICENSE
generated
vendored
Normal file
18
vendor/google.golang.org/api/googleapi/internal/uritemplates/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Copyright (c) 2013 Joshua Tacoma
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
248
vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go
generated
vendored
Normal file
248
vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go
generated
vendored
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
// Copyright 2013 Joshua Tacoma. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package uritemplates is a level 3 implementation of RFC 6570 (URI
|
||||||
|
// Template, http://tools.ietf.org/html/rfc6570).
|
||||||
|
// uritemplates does not support composite values (in Go: slices or maps)
|
||||||
|
// and so does not qualify as a level 4 implementation.
|
||||||
|
package uritemplates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
|
||||||
|
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
|
||||||
|
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
|
||||||
|
hex = []byte("0123456789ABCDEF")
|
||||||
|
)
|
||||||
|
|
||||||
|
func pctEncode(src []byte) []byte {
|
||||||
|
dst := make([]byte, len(src)*3)
|
||||||
|
for i, b := range src {
|
||||||
|
buf := dst[i*3 : i*3+3]
|
||||||
|
buf[0] = 0x25
|
||||||
|
buf[1] = hex[b/16]
|
||||||
|
buf[2] = hex[b%16]
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// pairWriter is a convenience struct which allows escaped and unescaped
|
||||||
|
// versions of the template to be written in parallel.
|
||||||
|
type pairWriter struct {
|
||||||
|
escaped, unescaped bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the provided string directly without any escaping.
|
||||||
|
func (w *pairWriter) Write(s string) {
|
||||||
|
w.escaped.WriteString(s)
|
||||||
|
w.unescaped.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape writes the provided string, escaping the string for the
|
||||||
|
// escaped output.
|
||||||
|
func (w *pairWriter) Escape(s string, allowReserved bool) {
|
||||||
|
w.unescaped.WriteString(s)
|
||||||
|
if allowReserved {
|
||||||
|
w.escaped.Write(reserved.ReplaceAllFunc([]byte(s), pctEncode))
|
||||||
|
} else {
|
||||||
|
w.escaped.Write(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escaped returns the escaped string.
|
||||||
|
func (w *pairWriter) Escaped() string {
|
||||||
|
return w.escaped.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unescaped returns the unescaped string.
|
||||||
|
func (w *pairWriter) Unescaped() string {
|
||||||
|
return w.unescaped.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A uriTemplate is a parsed representation of a URI template.
|
||||||
|
type uriTemplate struct {
|
||||||
|
raw string
|
||||||
|
parts []templatePart
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse parses a URI template string into a uriTemplate object.
|
||||||
|
func parse(rawTemplate string) (*uriTemplate, error) {
|
||||||
|
split := strings.Split(rawTemplate, "{")
|
||||||
|
parts := make([]templatePart, len(split)*2-1)
|
||||||
|
for i, s := range split {
|
||||||
|
if i == 0 {
|
||||||
|
if strings.Contains(s, "}") {
|
||||||
|
return nil, errors.New("unexpected }")
|
||||||
|
}
|
||||||
|
parts[i].raw = s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subsplit := strings.Split(s, "}")
|
||||||
|
if len(subsplit) != 2 {
|
||||||
|
return nil, errors.New("malformed template")
|
||||||
|
}
|
||||||
|
expression := subsplit[0]
|
||||||
|
var err error
|
||||||
|
parts[i*2-1], err = parseExpression(expression)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parts[i*2].raw = subsplit[1]
|
||||||
|
}
|
||||||
|
return &uriTemplate{
|
||||||
|
raw: rawTemplate,
|
||||||
|
parts: parts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type templatePart struct {
|
||||||
|
raw string
|
||||||
|
terms []templateTerm
|
||||||
|
first string
|
||||||
|
sep string
|
||||||
|
named bool
|
||||||
|
ifemp string
|
||||||
|
allowReserved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type templateTerm struct {
|
||||||
|
name string
|
||||||
|
explode bool
|
||||||
|
truncate int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExpression(expression string) (result templatePart, err error) {
|
||||||
|
switch expression[0] {
|
||||||
|
case '+':
|
||||||
|
result.sep = ","
|
||||||
|
result.allowReserved = true
|
||||||
|
expression = expression[1:]
|
||||||
|
case '.':
|
||||||
|
result.first = "."
|
||||||
|
result.sep = "."
|
||||||
|
expression = expression[1:]
|
||||||
|
case '/':
|
||||||
|
result.first = "/"
|
||||||
|
result.sep = "/"
|
||||||
|
expression = expression[1:]
|
||||||
|
case ';':
|
||||||
|
result.first = ";"
|
||||||
|
result.sep = ";"
|
||||||
|
result.named = true
|
||||||
|
expression = expression[1:]
|
||||||
|
case '?':
|
||||||
|
result.first = "?"
|
||||||
|
result.sep = "&"
|
||||||
|
result.named = true
|
||||||
|
result.ifemp = "="
|
||||||
|
expression = expression[1:]
|
||||||
|
case '&':
|
||||||
|
result.first = "&"
|
||||||
|
result.sep = "&"
|
||||||
|
result.named = true
|
||||||
|
result.ifemp = "="
|
||||||
|
expression = expression[1:]
|
||||||
|
case '#':
|
||||||
|
result.first = "#"
|
||||||
|
result.sep = ","
|
||||||
|
result.allowReserved = true
|
||||||
|
expression = expression[1:]
|
||||||
|
default:
|
||||||
|
result.sep = ","
|
||||||
|
}
|
||||||
|
rawterms := strings.Split(expression, ",")
|
||||||
|
result.terms = make([]templateTerm, len(rawterms))
|
||||||
|
for i, raw := range rawterms {
|
||||||
|
result.terms[i], err = parseTerm(raw)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTerm(term string) (result templateTerm, err error) {
|
||||||
|
// TODO(djd): Remove "*" suffix parsing once we check that no APIs have
|
||||||
|
// mistakenly used that attribute.
|
||||||
|
if strings.HasSuffix(term, "*") {
|
||||||
|
result.explode = true
|
||||||
|
term = term[:len(term)-1]
|
||||||
|
}
|
||||||
|
split := strings.Split(term, ":")
|
||||||
|
if len(split) == 1 {
|
||||||
|
result.name = term
|
||||||
|
} else if len(split) == 2 {
|
||||||
|
result.name = split[0]
|
||||||
|
var parsed int64
|
||||||
|
parsed, err = strconv.ParseInt(split[1], 10, 0)
|
||||||
|
result.truncate = int(parsed)
|
||||||
|
} else {
|
||||||
|
err = errors.New("multiple colons in same term")
|
||||||
|
}
|
||||||
|
if !validname.MatchString(result.name) {
|
||||||
|
err = errors.New("not a valid name: " + result.name)
|
||||||
|
}
|
||||||
|
if result.explode && result.truncate > 0 {
|
||||||
|
err = errors.New("both explode and prefix modifers on same term")
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand expands a URI template with a set of values to produce the
|
||||||
|
// resultant URI. Two forms of the result are returned: one with all the
|
||||||
|
// elements escaped, and one with the elements unescaped.
|
||||||
|
func (t *uriTemplate) Expand(values map[string]string) (escaped, unescaped string) {
|
||||||
|
var w pairWriter
|
||||||
|
for _, p := range t.parts {
|
||||||
|
p.expand(&w, values)
|
||||||
|
}
|
||||||
|
return w.Escaped(), w.Unescaped()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *templatePart) expand(w *pairWriter, values map[string]string) {
|
||||||
|
if len(tp.raw) > 0 {
|
||||||
|
w.Write(tp.raw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var first = true
|
||||||
|
for _, term := range tp.terms {
|
||||||
|
value, exists := values[term.name]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
w.Write(tp.first)
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
w.Write(tp.sep)
|
||||||
|
}
|
||||||
|
tp.expandString(w, term, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *templatePart) expandName(w *pairWriter, name string, empty bool) {
|
||||||
|
if tp.named {
|
||||||
|
w.Write(name)
|
||||||
|
if empty {
|
||||||
|
w.Write(tp.ifemp)
|
||||||
|
} else {
|
||||||
|
w.Write("=")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *templatePart) expandString(w *pairWriter, t templateTerm, s string) {
|
||||||
|
if len(s) > t.truncate && t.truncate > 0 {
|
||||||
|
s = s[:t.truncate]
|
||||||
|
}
|
||||||
|
tp.expandName(w, t.name, len(s) == 0)
|
||||||
|
w.Escape(s, tp.allowReserved)
|
||||||
|
}
|
17
vendor/google.golang.org/api/googleapi/internal/uritemplates/utils.go
generated
vendored
Normal file
17
vendor/google.golang.org/api/googleapi/internal/uritemplates/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uritemplates
|
||||||
|
|
||||||
|
// Expand parses then expands a URI template with a set of values to produce
|
||||||
|
// the resultant URI. Two forms of the result are returned: one with all the
|
||||||
|
// elements escaped, and one with the elements unescaped.
|
||||||
|
func Expand(path string, values map[string]string) (escaped, unescaped string, err error) {
|
||||||
|
template, err := parse(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
escaped, unescaped = template.Expand(values)
|
||||||
|
return escaped, unescaped, nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2012 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package transport contains HTTP transports used to make
|
||||||
|
// authenticated API requests.
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKey is an HTTP Transport which wraps an underlying transport and
|
||||||
|
// appends an API Key "key" parameter to the URL of outgoing requests.
|
||||||
|
type APIKey struct {
|
||||||
|
// Key is the API Key to set on requests.
|
||||||
|
Key string
|
||||||
|
|
||||||
|
// Transport is the underlying HTTP transport.
|
||||||
|
// If nil, http.DefaultTransport is used.
|
||||||
|
Transport http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *APIKey) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
rt := t.Transport
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
if rt == nil {
|
||||||
|
return nil, errors.New("googleapi/transport: no Transport specified or available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newReq := *req
|
||||||
|
args := newReq.URL.Query()
|
||||||
|
args.Set("key", t.Key)
|
||||||
|
newReq.URL.RawQuery = args.Encode()
|
||||||
|
return rt.RoundTrip(&newReq)
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2013 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package googleapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Int64s is a slice of int64s that marshal as quoted strings in JSON.
|
||||||
|
type Int64s []int64
|
||||||
|
|
||||||
|
func (q *Int64s) UnmarshalJSON(raw []byte) error {
|
||||||
|
*q = (*q)[:0]
|
||||||
|
var ss []string
|
||||||
|
if err := json.Unmarshal(raw, &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
v, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*q = append(*q, int64(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32s is a slice of int32s that marshal as quoted strings in JSON.
|
||||||
|
type Int32s []int32
|
||||||
|
|
||||||
|
func (q *Int32s) UnmarshalJSON(raw []byte) error {
|
||||||
|
*q = (*q)[:0]
|
||||||
|
var ss []string
|
||||||
|
if err := json.Unmarshal(raw, &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
v, err := strconv.ParseInt(s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*q = append(*q, int32(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64s is a slice of uint64s that marshal as quoted strings in JSON.
|
||||||
|
type Uint64s []uint64
|
||||||
|
|
||||||
|
func (q *Uint64s) UnmarshalJSON(raw []byte) error {
|
||||||
|
*q = (*q)[:0]
|
||||||
|
var ss []string
|
||||||
|
if err := json.Unmarshal(raw, &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
v, err := strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*q = append(*q, uint64(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32s is a slice of uint32s that marshal as quoted strings in JSON.
|
||||||
|
type Uint32s []uint32
|
||||||
|
|
||||||
|
func (q *Uint32s) UnmarshalJSON(raw []byte) error {
|
||||||
|
*q = (*q)[:0]
|
||||||
|
var ss []string
|
||||||
|
if err := json.Unmarshal(raw, &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
v, err := strconv.ParseUint(s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*q = append(*q, uint32(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s is a slice of float64s that marshal as quoted strings in JSON.
|
||||||
|
type Float64s []float64
|
||||||
|
|
||||||
|
func (q *Float64s) UnmarshalJSON(raw []byte) error {
|
||||||
|
*q = (*q)[:0]
|
||||||
|
var ss []string
|
||||||
|
if err := json.Unmarshal(raw, &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*q = append(*q, float64(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quotedList(n int, fn func(dst []byte, i int) []byte) ([]byte, error) {
|
||||||
|
dst := make([]byte, 0, 2+n*10) // somewhat arbitrary
|
||||||
|
dst = append(dst, '[')
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
dst = append(dst, ',')
|
||||||
|
}
|
||||||
|
dst = append(dst, '"')
|
||||||
|
dst = fn(dst, i)
|
||||||
|
dst = append(dst, '"')
|
||||||
|
}
|
||||||
|
dst = append(dst, ']')
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Int64s) MarshalJSON() ([]byte, error) {
|
||||||
|
return quotedList(len(s), func(dst []byte, i int) []byte {
|
||||||
|
return strconv.AppendInt(dst, s[i], 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Int32s) MarshalJSON() ([]byte, error) {
|
||||||
|
return quotedList(len(s), func(dst []byte, i int) []byte {
|
||||||
|
return strconv.AppendInt(dst, int64(s[i]), 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Uint64s) MarshalJSON() ([]byte, error) {
|
||||||
|
return quotedList(len(s), func(dst []byte, i int) []byte {
|
||||||
|
return strconv.AppendUint(dst, s[i], 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Uint32s) MarshalJSON() ([]byte, error) {
|
||||||
|
return quotedList(len(s), func(dst []byte, i int) []byte {
|
||||||
|
return strconv.AppendUint(dst, uint64(s[i]), 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Float64s) MarshalJSON() ([]byte, error) {
|
||||||
|
return quotedList(len(s), func(dst []byte, i int) []byte {
|
||||||
|
return strconv.AppendFloat(dst, s[i], 'g', -1, 64)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Bool is a helper routine that allocates a new bool value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Bool(v bool) *bool { return &v }
|
||||||
|
|
||||||
|
// Int32 is a helper routine that allocates a new int32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int32(v int32) *int32 { return &v }
|
||||||
|
|
||||||
|
// Int64 is a helper routine that allocates a new int64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int64(v int64) *int64 { return &v }
|
||||||
|
|
||||||
|
// Float64 is a helper routine that allocates a new float64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Float64(v float64) *float64 { return &v }
|
||||||
|
|
||||||
|
// Uint32 is a helper routine that allocates a new uint32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint32(v uint32) *uint32 { return &v }
|
||||||
|
|
||||||
|
// Uint64 is a helper routine that allocates a new uint64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint64(v uint64) *uint64 { return &v }
|
||||||
|
|
||||||
|
// String is a helper routine that allocates a new string value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func String(v string) *string { return &v }
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2016 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 internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"google.golang.org/grpc/naming"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PoolResolver provides a fixed list of addresses to load balance between
|
||||||
|
// and does not provide further updates.
|
||||||
|
type PoolResolver struct {
|
||||||
|
poolSize int
|
||||||
|
dialOpt *DialSettings
|
||||||
|
ch chan []*naming.Update
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPoolResolver returns a PoolResolver
|
||||||
|
// This is an EXPERIMENTAL API and may be changed or removed in the future.
|
||||||
|
func NewPoolResolver(size int, o *DialSettings) *PoolResolver {
|
||||||
|
return &PoolResolver{poolSize: size, dialOpt: o}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve returns a Watcher for the endpoint defined by the DialSettings
|
||||||
|
// provided to NewPoolResolver.
|
||||||
|
func (r *PoolResolver) Resolve(target string) (naming.Watcher, error) {
|
||||||
|
if r.dialOpt.Endpoint == "" {
|
||||||
|
return nil, errors.New("No endpoint configured")
|
||||||
|
}
|
||||||
|
addrs := make([]*naming.Update, 0, r.poolSize)
|
||||||
|
for i := 0; i < r.poolSize; i++ {
|
||||||
|
addrs = append(addrs, &naming.Update{Op: naming.Add, Addr: r.dialOpt.Endpoint, Metadata: i})
|
||||||
|
}
|
||||||
|
r.ch = make(chan []*naming.Update, 1)
|
||||||
|
r.ch <- addrs
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns a static list of updates on the first call,
|
||||||
|
// and blocks indefinitely until Close is called on subsequent calls.
|
||||||
|
func (r *PoolResolver) Next() ([]*naming.Update, error) {
|
||||||
|
return <-r.ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PoolResolver) Close() {
|
||||||
|
close(r.ch)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Package internal supports the options and transport packages.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialSettings holds information needed to establish a connection with a
|
||||||
|
// Google API service.
|
||||||
|
type DialSettings struct {
|
||||||
|
Endpoint string
|
||||||
|
Scopes []string
|
||||||
|
ServiceAccountJSONFilename string // if set, TokenSource is ignored.
|
||||||
|
TokenSource oauth2.TokenSource
|
||||||
|
UserAgent string
|
||||||
|
APIKey string
|
||||||
|
HTTPClient *http.Client
|
||||||
|
GRPCDialOpts []grpc.DialOption
|
||||||
|
GRPCConn *grpc.ClientConn
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
// Copyright 2016 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 iterator provides support for standard Google API iterators.
|
||||||
|
// See https://github.com/GoogleCloudPlatform/gcloud-golang/wiki/Iterator-Guidelines.
|
||||||
|
package iterator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Done is returned by an iterator's Next method when the iteration is
|
||||||
|
// complete; when there are no more items to return.
|
||||||
|
var Done = errors.New("no more items in iterator")
|
||||||
|
|
||||||
|
// We don't support mixed calls to Next and NextPage because they play
|
||||||
|
// with the paging state in incompatible ways.
|
||||||
|
var errMixed = errors.New("iterator: Next and NextPage called on same iterator")
|
||||||
|
|
||||||
|
// PageInfo contains information about an iterator's paging state.
|
||||||
|
type PageInfo struct {
|
||||||
|
// Token is the token used to retrieve the next page of items from the
|
||||||
|
// API. You may set Token immediately after creating an iterator to
|
||||||
|
// begin iteration at a particular point. If Token is the empty string,
|
||||||
|
// the iterator will begin with the first eligible item.
|
||||||
|
//
|
||||||
|
// The result of setting Token after the first call to Next is undefined.
|
||||||
|
//
|
||||||
|
// After the underlying API method is called to retrieve a page of items,
|
||||||
|
// Token is set to the next-page token in the response.
|
||||||
|
Token string
|
||||||
|
|
||||||
|
// MaxSize is the maximum number of items returned by a call to the API.
|
||||||
|
// Set MaxSize as a hint to optimize the buffering behavior of the iterator.
|
||||||
|
// If zero, the page size is determined by the underlying service.
|
||||||
|
//
|
||||||
|
// Use Pager to retrieve a page of a specific, exact size.
|
||||||
|
MaxSize int
|
||||||
|
|
||||||
|
// The error state of the iterator. Manipulated by PageInfo.next and Pager.
|
||||||
|
// This is a latch: it starts as nil, and once set should never change.
|
||||||
|
err error
|
||||||
|
|
||||||
|
// If true, no more calls to fetch should be made. Set to true when fetch
|
||||||
|
// returns an empty page token. The iterator is Done when this is true AND
|
||||||
|
// the buffer is empty.
|
||||||
|
atEnd bool
|
||||||
|
|
||||||
|
// Function that fetches a page from the underlying service. It should pass
|
||||||
|
// the pageSize and pageToken arguments to the service, fill the buffer
|
||||||
|
// with the results from the call, and return the next-page token returned
|
||||||
|
// by the service. The function must not remove any existing items from the
|
||||||
|
// buffer. If the underlying RPC takes an int32 page size, pageSize should
|
||||||
|
// be silently truncated.
|
||||||
|
fetch func(pageSize int, pageToken string) (nextPageToken string, err error)
|
||||||
|
|
||||||
|
// Function that clears the iterator's buffer, returning any currently buffered items.
|
||||||
|
bufLen func() int
|
||||||
|
|
||||||
|
// Function that returns the buffer, after setting the buffer variable to nil.
|
||||||
|
takeBuf func() interface{}
|
||||||
|
|
||||||
|
// Set to true on first call to PageInfo.next or Pager.NextPage. Used to check
|
||||||
|
// for calls to both Next and NextPage with the same iterator.
|
||||||
|
nextCalled, nextPageCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPageInfo exposes internals for iterator implementations.
|
||||||
|
// It is not a stable interface.
|
||||||
|
var NewPageInfo = newPageInfo
|
||||||
|
|
||||||
|
// If an iterator can support paging, its iterator-creating method should call
|
||||||
|
// this (via the NewPageInfo variable above).
|
||||||
|
//
|
||||||
|
// The fetch, bufLen and takeBuf arguments provide access to the
|
||||||
|
// iterator's internal slice of buffered items. They behave as described in
|
||||||
|
// PageInfo, above.
|
||||||
|
//
|
||||||
|
// The return value is the PageInfo.next method bound to the returned PageInfo value.
|
||||||
|
// (Returning it avoids exporting PageInfo.next.)
|
||||||
|
func newPageInfo(fetch func(int, string) (string, error), bufLen func() int, takeBuf func() interface{}) (*PageInfo, func() error) {
|
||||||
|
pi := &PageInfo{
|
||||||
|
fetch: fetch,
|
||||||
|
bufLen: bufLen,
|
||||||
|
takeBuf: takeBuf,
|
||||||
|
}
|
||||||
|
return pi, pi.next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining returns the number of items available before the iterator makes another API call.
|
||||||
|
func (pi *PageInfo) Remaining() int { return pi.bufLen() }
|
||||||
|
|
||||||
|
// next provides support for an iterator's Next function. An iterator's Next
|
||||||
|
// should return the error returned by next if non-nil; else it can assume
|
||||||
|
// there is at least one item in its buffer, and it should return that item and
|
||||||
|
// remove it from the buffer.
|
||||||
|
func (pi *PageInfo) next() error {
|
||||||
|
pi.nextCalled = true
|
||||||
|
if pi.err != nil { // Once we get an error, always return it.
|
||||||
|
// TODO(jba): fix so users can retry on transient errors? Probably not worth it.
|
||||||
|
return pi.err
|
||||||
|
}
|
||||||
|
if pi.nextPageCalled {
|
||||||
|
pi.err = errMixed
|
||||||
|
return pi.err
|
||||||
|
}
|
||||||
|
// Loop until we get some items or reach the end.
|
||||||
|
for pi.bufLen() == 0 && !pi.atEnd {
|
||||||
|
if err := pi.fill(pi.MaxSize); err != nil {
|
||||||
|
pi.err = err
|
||||||
|
return pi.err
|
||||||
|
}
|
||||||
|
if pi.Token == "" {
|
||||||
|
pi.atEnd = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Either the buffer is non-empty or pi.atEnd is true (or both).
|
||||||
|
if pi.bufLen() == 0 {
|
||||||
|
// The buffer is empty and pi.atEnd is true, i.e. the service has no
|
||||||
|
// more items.
|
||||||
|
pi.err = Done
|
||||||
|
}
|
||||||
|
return pi.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the service to fill the buffer, using size and pi.Token. Set pi.Token to the
|
||||||
|
// next-page token returned by the call.
|
||||||
|
// If fill returns a non-nil error, the buffer will be empty.
|
||||||
|
func (pi *PageInfo) fill(size int) error {
|
||||||
|
tok, err := pi.fetch(size, pi.Token)
|
||||||
|
if err != nil {
|
||||||
|
pi.takeBuf() // clear the buffer
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pi.Token = tok
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pageable is implemented by iterators that support paging.
|
||||||
|
type Pageable interface {
|
||||||
|
// PageInfo returns paging information associated with the iterator.
|
||||||
|
PageInfo() *PageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pager supports retrieving iterator items a page at a time.
|
||||||
|
type Pager struct {
|
||||||
|
pageInfo *PageInfo
|
||||||
|
pageSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPager returns a pager that uses iter. Calls to its NextPage method will
|
||||||
|
// obtain exactly pageSize items, unless fewer remain. The pageToken argument
|
||||||
|
// indicates where to start the iteration. Pass the empty string to start at
|
||||||
|
// the beginning, or pass a token retrieved from a call to Pager.NextPage.
|
||||||
|
//
|
||||||
|
// If you use an iterator with a Pager, you must not call Next on the iterator.
|
||||||
|
func NewPager(iter Pageable, pageSize int, pageToken string) *Pager {
|
||||||
|
p := &Pager{
|
||||||
|
pageInfo: iter.PageInfo(),
|
||||||
|
pageSize: pageSize,
|
||||||
|
}
|
||||||
|
p.pageInfo.Token = pageToken
|
||||||
|
if pageSize <= 0 {
|
||||||
|
p.pageInfo.err = errors.New("iterator: page size must be positive")
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPage retrieves a sequence of items from the iterator and appends them
|
||||||
|
// to slicep, which must be a pointer to a slice of the iterator's item type.
|
||||||
|
// Exactly p.pageSize items will be appended, unless fewer remain.
|
||||||
|
//
|
||||||
|
// The first return value is the page token to use for the next page of items.
|
||||||
|
// If empty, there are no more pages. Aside from checking for the end of the
|
||||||
|
// iteration, the returned page token is only needed if the iteration is to be
|
||||||
|
// resumed a later time, in another context (possibly another process).
|
||||||
|
//
|
||||||
|
// The second return value is non-nil if an error occurred. It will never be
|
||||||
|
// the special iterator sentinel value Done. To recognize the end of the
|
||||||
|
// iteration, compare nextPageToken to the empty string.
|
||||||
|
//
|
||||||
|
// It is possible for NextPage to return a single zero-length page along with
|
||||||
|
// an empty page token when there are no more items in the iteration.
|
||||||
|
func (p *Pager) NextPage(slicep interface{}) (nextPageToken string, err error) {
|
||||||
|
p.pageInfo.nextPageCalled = true
|
||||||
|
if p.pageInfo.err != nil {
|
||||||
|
return "", p.pageInfo.err
|
||||||
|
}
|
||||||
|
if p.pageInfo.nextCalled {
|
||||||
|
p.pageInfo.err = errMixed
|
||||||
|
return "", p.pageInfo.err
|
||||||
|
}
|
||||||
|
if p.pageInfo.bufLen() > 0 {
|
||||||
|
return "", errors.New("must call NextPage with an empty buffer")
|
||||||
|
}
|
||||||
|
// The buffer must be empty here, so takeBuf is a no-op. We call it just to get
|
||||||
|
// the buffer's type.
|
||||||
|
wantSliceType := reflect.PtrTo(reflect.ValueOf(p.pageInfo.takeBuf()).Type())
|
||||||
|
if slicep == nil {
|
||||||
|
return "", errors.New("nil passed to Pager.NextPage")
|
||||||
|
}
|
||||||
|
vslicep := reflect.ValueOf(slicep)
|
||||||
|
if vslicep.Type() != wantSliceType {
|
||||||
|
return "", fmt.Errorf("slicep should be of type %s, got %T", wantSliceType, slicep)
|
||||||
|
}
|
||||||
|
for p.pageInfo.bufLen() < p.pageSize {
|
||||||
|
if err := p.pageInfo.fill(p.pageSize - p.pageInfo.bufLen()); err != nil {
|
||||||
|
p.pageInfo.err = err
|
||||||
|
return "", p.pageInfo.err
|
||||||
|
}
|
||||||
|
if p.pageInfo.Token == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e := vslicep.Elem()
|
||||||
|
e.Set(reflect.AppendSlice(e, reflect.ValueOf(p.pageInfo.takeBuf())))
|
||||||
|
return p.pageInfo.Token, nil
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Package option contains options for Google API clients.
|
||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"google.golang.org/api/internal"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ClientOption is an option for a Google API client.
|
||||||
|
type ClientOption interface {
|
||||||
|
Apply(*internal.DialSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTokenSource returns a ClientOption that specifies an OAuth2 token
|
||||||
|
// source to be used as the basis for authentication.
|
||||||
|
func WithTokenSource(s oauth2.TokenSource) ClientOption {
|
||||||
|
return withTokenSource{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withTokenSource struct{ ts oauth2.TokenSource }
|
||||||
|
|
||||||
|
func (w withTokenSource) Apply(o *internal.DialSettings) {
|
||||||
|
o.TokenSource = w.ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAccountFile returns a ClientOption that uses a Google service
|
||||||
|
// account credentials file to authenticate.
|
||||||
|
// Use WithTokenSource with a token source created from
|
||||||
|
// golang.org/x/oauth2/google.JWTConfigFromJSON
|
||||||
|
// if reading the file from disk is not an option.
|
||||||
|
func WithServiceAccountFile(filename string) ClientOption {
|
||||||
|
return withServiceAccountFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withServiceAccountFile string
|
||||||
|
|
||||||
|
func (w withServiceAccountFile) Apply(o *internal.DialSettings) {
|
||||||
|
o.ServiceAccountJSONFilename = string(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEndpoint returns a ClientOption that overrides the default endpoint
|
||||||
|
// to be used for a service.
|
||||||
|
func WithEndpoint(url string) ClientOption {
|
||||||
|
return withEndpoint(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withEndpoint string
|
||||||
|
|
||||||
|
func (w withEndpoint) Apply(o *internal.DialSettings) {
|
||||||
|
o.Endpoint = string(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithScopes returns a ClientOption that overrides the default OAuth2 scopes
|
||||||
|
// to be used for a service.
|
||||||
|
func WithScopes(scope ...string) ClientOption {
|
||||||
|
return withScopes(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withScopes []string
|
||||||
|
|
||||||
|
func (w withScopes) Apply(o *internal.DialSettings) {
|
||||||
|
s := make([]string, len(w))
|
||||||
|
copy(s, w)
|
||||||
|
o.Scopes = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserAgent returns a ClientOption that sets the User-Agent.
|
||||||
|
func WithUserAgent(ua string) ClientOption {
|
||||||
|
return withUA(ua)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withUA string
|
||||||
|
|
||||||
|
func (w withUA) Apply(o *internal.DialSettings) { o.UserAgent = string(w) }
|
||||||
|
|
||||||
|
// WithHTTPClient returns a ClientOption that specifies the HTTP client to use
|
||||||
|
// as the basis of communications. This option may only be used with services
|
||||||
|
// that support HTTP as their communication transport. When used, the
|
||||||
|
// WithHTTPClient option takes precedent over all other supplied options.
|
||||||
|
func WithHTTPClient(client *http.Client) ClientOption {
|
||||||
|
return withHTTPClient{client}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withHTTPClient struct{ client *http.Client }
|
||||||
|
|
||||||
|
func (w withHTTPClient) Apply(o *internal.DialSettings) {
|
||||||
|
o.HTTPClient = w.client
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGRPCConn returns a ClientOption that specifies the gRPC client
|
||||||
|
// connection to use as the basis of communications. This option many only be
|
||||||
|
// used with services that support gRPC as their communication transport. When
|
||||||
|
// used, the WithGRPCConn option takes precedent over all other supplied
|
||||||
|
// options.
|
||||||
|
func WithGRPCConn(conn *grpc.ClientConn) ClientOption {
|
||||||
|
return withGRPCConn{conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withGRPCConn struct{ conn *grpc.ClientConn }
|
||||||
|
|
||||||
|
func (w withGRPCConn) Apply(o *internal.DialSettings) {
|
||||||
|
o.GRPCConn = w.conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGRPCDialOption returns a ClientOption that appends a new grpc.DialOption
|
||||||
|
// to an underlying gRPC dial. It does not work with WithGRPCConn.
|
||||||
|
func WithGRPCDialOption(opt grpc.DialOption) ClientOption {
|
||||||
|
return withGRPCDialOption{opt}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withGRPCDialOption struct{ opt grpc.DialOption }
|
||||||
|
|
||||||
|
func (w withGRPCDialOption) Apply(o *internal.DialSettings) {
|
||||||
|
o.GRPCDialOpts = append(o.GRPCDialOpts, w.opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGRPCConnectionPool returns a ClientOption that creates a pool of gRPC
|
||||||
|
// connections that requests will be balanced between.
|
||||||
|
// This is an EXPERIMENTAL API and may be changed or removed in the future.
|
||||||
|
func WithGRPCConnectionPool(size int) ClientOption {
|
||||||
|
return withGRPCConnectionPool(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withGRPCConnectionPool int
|
||||||
|
|
||||||
|
func (w withGRPCConnectionPool) Apply(o *internal.DialSettings) {
|
||||||
|
balancer := grpc.RoundRobin(internal.NewPoolResolver(int(w), o))
|
||||||
|
o.GRPCDialOpts = append(o.GRPCDialOpts, grpc.WithBalancer(balancer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAPIKey returns a ClientOption that specifies an API key to be used
|
||||||
|
// as the basis for authentication.
|
||||||
|
func WithAPIKey(apiKey string) ClientOption {
|
||||||
|
return withAPIKey(apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
type withAPIKey string
|
||||||
|
|
||||||
|
func (w withAPIKey) Apply(o *internal.DialSettings) { o.APIKey = string(w) }
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright 2015 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 transport supports network connections to HTTP and GRPC servers.
|
||||||
|
// This package is not intended for use by end developers. Use the
|
||||||
|
// google.golang.org/api/option package to configure API clients.
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/oauth"
|
||||||
|
|
||||||
|
gtransport "google.golang.org/api/googleapi/transport"
|
||||||
|
"google.golang.org/api/internal"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHTTPClient returns an HTTP client for use communicating with a Google cloud
|
||||||
|
// service, configured with the given ClientOptions. It also returns the endpoint
|
||||||
|
// for the service as specified in the options.
|
||||||
|
func NewHTTPClient(ctx context.Context, opts ...option.ClientOption) (*http.Client, string, error) {
|
||||||
|
var o internal.DialSettings
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.Apply(&o)
|
||||||
|
}
|
||||||
|
if o.GRPCConn != nil {
|
||||||
|
return nil, "", errors.New("unsupported gRPC connection specified")
|
||||||
|
}
|
||||||
|
// TODO(djd): Set UserAgent on all outgoing requests.
|
||||||
|
if o.HTTPClient != nil {
|
||||||
|
return o.HTTPClient, o.Endpoint, nil
|
||||||
|
}
|
||||||
|
if o.APIKey != "" {
|
||||||
|
hc := &http.Client{
|
||||||
|
Transport: >ransport.APIKey{
|
||||||
|
Key: o.APIKey,
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return hc, o.Endpoint, nil
|
||||||
|
}
|
||||||
|
if o.ServiceAccountJSONFilename != "" {
|
||||||
|
ts, err := serviceAcctTokenSource(ctx, o.ServiceAccountJSONFilename, o.Scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
o.TokenSource = ts
|
||||||
|
}
|
||||||
|
if o.TokenSource == nil {
|
||||||
|
var err error
|
||||||
|
o.TokenSource, err = google.DefaultTokenSource(ctx, o.Scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("google.DefaultTokenSource: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oauth2.NewClient(ctx, o.TokenSource), o.Endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set at init time by dial_appengine.go. If nil, we're not on App Engine.
|
||||||
|
var appengineDialerHook func(context.Context) grpc.DialOption
|
||||||
|
|
||||||
|
// DialGRPC returns a GRPC connection for use communicating with a Google cloud
|
||||||
|
// service, configured with the given ClientOptions.
|
||||||
|
func DialGRPC(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
|
||||||
|
var o internal.DialSettings
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.Apply(&o)
|
||||||
|
}
|
||||||
|
if o.HTTPClient != nil {
|
||||||
|
return nil, errors.New("unsupported HTTP client specified")
|
||||||
|
}
|
||||||
|
if o.GRPCConn != nil {
|
||||||
|
return o.GRPCConn, nil
|
||||||
|
}
|
||||||
|
if o.ServiceAccountJSONFilename != "" {
|
||||||
|
ts, err := serviceAcctTokenSource(ctx, o.ServiceAccountJSONFilename, o.Scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o.TokenSource = ts
|
||||||
|
}
|
||||||
|
if o.TokenSource == nil {
|
||||||
|
var err error
|
||||||
|
o.TokenSource, err = google.DefaultTokenSource(ctx, o.Scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google.DefaultTokenSource: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grpcOpts := []grpc.DialOption{
|
||||||
|
grpc.WithPerRPCCredentials(oauth.TokenSource{o.TokenSource}),
|
||||||
|
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
|
||||||
|
}
|
||||||
|
if appengineDialerHook != nil {
|
||||||
|
// Use the Socket API on App Engine.
|
||||||
|
grpcOpts = append(grpcOpts, appengineDialerHook(ctx))
|
||||||
|
}
|
||||||
|
grpcOpts = append(grpcOpts, o.GRPCDialOpts...)
|
||||||
|
if o.UserAgent != "" {
|
||||||
|
grpcOpts = append(grpcOpts, grpc.WithUserAgent(o.UserAgent))
|
||||||
|
}
|
||||||
|
return grpc.DialContext(ctx, o.Endpoint, grpcOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceAcctTokenSource(ctx context.Context, filename string, scope ...string) (oauth2.TokenSource, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read service account file: %v", err)
|
||||||
|
}
|
||||||
|
cfg, err := google.JWTConfigFromJSON(data, scope...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
|
||||||
|
}
|
||||||
|
return cfg.TokenSource(ctx), nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2016 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 appengine
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/appengine/socket"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineDialerHook = func(ctx context.Context) grpc.DialOption {
|
||||||
|
return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
return socket.DialTimeout(ctx, "tcp", addr, timeout)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Go App Engine packages
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/golang/appengine.svg)](https://travis-ci.org/golang/appengine)
|
||||||
|
|
||||||
|
This repository supports the Go runtime on App Engine,
|
||||||
|
including both the standard App Engine and the
|
||||||
|
"App Engine flexible environment" (formerly known as "Managed VMs").
|
||||||
|
It provides APIs for interacting with App Engine services.
|
||||||
|
Its canonical import path is `google.golang.org/appengine`.
|
||||||
|
|
||||||
|
See https://cloud.google.com/appengine/docs/go/
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
File issue reports and feature requests on the [Google App Engine issue
|
||||||
|
tracker](https://code.google.com/p/googleappengine/issues/entry?template=Go%20defect).
|
||||||
|
|
||||||
|
## Directory structure
|
||||||
|
The top level directory of this repository is the `appengine` package. It
|
||||||
|
contains the
|
||||||
|
basic APIs (e.g. `appengine.NewContext`) that apply across APIs. Specific API
|
||||||
|
packages are in subdirectories (e.g. `datastore`).
|
||||||
|
|
||||||
|
There is an `internal` subdirectory that contains service protocol buffers,
|
||||||
|
plus packages required for connectivity to make API calls. App Engine apps
|
||||||
|
should not directly import any package under `internal`.
|
||||||
|
|
||||||
|
## Updating a Go App Engine app
|
||||||
|
|
||||||
|
This section describes how to update an older Go App Engine app to use
|
||||||
|
these packages. A provided tool, `aefix`, can help automate steps 2 and 3
|
||||||
|
(run `go get google.golang.org/appengine/cmd/aefix` to install it), but
|
||||||
|
read the details below since `aefix` can't perform all the changes.
|
||||||
|
|
||||||
|
### 1. Update YAML files (App Engine flexible environment / Managed VMs only)
|
||||||
|
|
||||||
|
The `app.yaml` file (and YAML files for modules) should have these new lines added:
|
||||||
|
```
|
||||||
|
vm: true
|
||||||
|
```
|
||||||
|
See https://cloud.google.com/appengine/docs/go/modules/#Go_Instance_scaling_and_class for details.
|
||||||
|
|
||||||
|
### 2. Update import paths
|
||||||
|
|
||||||
|
The import paths for App Engine packages are now fully qualified, based at `google.golang.org/appengine`.
|
||||||
|
You will need to update your code to use import paths starting with that; for instance,
|
||||||
|
code importing `appengine/datastore` will now need to import `google.golang.org/appengine/datastore`.
|
||||||
|
|
||||||
|
### 3. Update code using deprecated, removed or modified APIs
|
||||||
|
|
||||||
|
Most App Engine services are available with exactly the same API.
|
||||||
|
A few APIs were cleaned up, and some are not available yet.
|
||||||
|
This list summarises the differences:
|
||||||
|
|
||||||
|
* `appengine.Context` has been replaced with the `Context` type from `golang.org/x/net/context`.
|
||||||
|
* Logging methods that were on `appengine.Context` are now functions in `google.golang.org/appengine/log`.
|
||||||
|
* `appengine.Timeout` has been removed. Use `context.WithTimeout` instead.
|
||||||
|
* `appengine.Datacenter` now takes a `context.Context` argument.
|
||||||
|
* `datastore.PropertyLoadSaver` has been simplified to use slices in place of channels.
|
||||||
|
* `delay.Call` now returns an error.
|
||||||
|
* `search.FieldLoadSaver` now handles document metadata.
|
||||||
|
* `urlfetch.Transport` no longer has a Deadline field; set a deadline on the
|
||||||
|
`context.Context` instead.
|
||||||
|
* `aetest` no longer declares its own Context type, and uses the standard one instead.
|
||||||
|
* `taskqueue.QueueStats` no longer takes a maxTasks argument. That argument has been
|
||||||
|
deprecated and unused for a long time.
|
||||||
|
* `appengine.BackendHostname` and `appengine.BackendInstance` were for the deprecated backends feature.
|
||||||
|
Use `appengine.ModuleHostname`and `appengine.ModuleName` instead.
|
||||||
|
* Most of `appengine/file` and parts of `appengine/blobstore` are deprecated.
|
||||||
|
Use [Google Cloud Storage](https://godoc.org/cloud.google.com/go/storage) if the
|
||||||
|
feature you require is not present in the new
|
||||||
|
[blobstore package](https://google.golang.org/appengine/blobstore).
|
||||||
|
* `appengine/socket` is not required on App Engine flexible environment / Managed VMs.
|
||||||
|
Use the standard `net` package instead.
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package appengine provides basic functionality for Google App Engine.
|
||||||
|
//
|
||||||
|
// For more information on how to write Go apps for Google App Engine, see:
|
||||||
|
// https://cloud.google.com/appengine/docs/go/
|
||||||
|
package appengine // import "google.golang.org/appengine"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The gophers party all night; the rabbits provide the beats.
|
||||||
|
|
||||||
|
// Main is the principal entry point for an app running in App Engine.
|
||||||
|
//
|
||||||
|
// On App Engine Flexible it installs a trivial health checker if one isn't
|
||||||
|
// already registered, and starts listening on port 8080 (overridden by the
|
||||||
|
// $PORT environment variable).
|
||||||
|
//
|
||||||
|
// See https://cloud.google.com/appengine/docs/flexible/custom-runtimes#health_check_requests
|
||||||
|
// for details on how to do your own health checking.
|
||||||
|
//
|
||||||
|
// On App Engine Standard it ensures the server has started and is prepared to
|
||||||
|
// receive requests.
|
||||||
|
//
|
||||||
|
// Main never returns.
|
||||||
|
//
|
||||||
|
// Main is designed so that the app's main package looks like this:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "google.golang.org/appengine"
|
||||||
|
//
|
||||||
|
// _ "myapp/package0"
|
||||||
|
// _ "myapp/package1"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// appengine.Main()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The "myapp/packageX" packages are expected to register HTTP handlers
|
||||||
|
// in their init functions.
|
||||||
|
func Main() {
|
||||||
|
internal.Main()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDevAppServer reports whether the App Engine app is running in the
|
||||||
|
// development App Server.
|
||||||
|
func IsDevAppServer() bool {
|
||||||
|
return internal.IsDevAppServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a context for an in-flight HTTP request.
|
||||||
|
// This function is cheap.
|
||||||
|
func NewContext(req *http.Request) context.Context {
|
||||||
|
return WithContext(context.Background(), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext returns a copy of the parent context
|
||||||
|
// and associates it with an in-flight HTTP request.
|
||||||
|
// This function is cheap.
|
||||||
|
func WithContext(parent context.Context, req *http.Request) context.Context {
|
||||||
|
return internal.WithContext(parent, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dsymonds): Add a Call function here? Otherwise other packages can't access internal.Call.
|
||||||
|
|
||||||
|
// BlobKey is a key for a blobstore blob.
|
||||||
|
//
|
||||||
|
// Conceptually, this type belongs in the blobstore package, but it lives in
|
||||||
|
// the appengine package to avoid a circular dependency: blobstore depends on
|
||||||
|
// datastore, and datastore needs to refer to the BlobKey type.
|
||||||
|
type BlobKey string
|
||||||
|
|
||||||
|
// GeoPoint represents a location as latitude/longitude in degrees.
|
||||||
|
type GeoPoint struct {
|
||||||
|
Lat, Lng float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude.
|
||||||
|
func (g GeoPoint) Valid() bool {
|
||||||
|
return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180
|
||||||
|
}
|
||||||
|
|
||||||
|
// APICallFunc defines a function type for handling an API call.
|
||||||
|
// See WithCallOverride.
|
||||||
|
type APICallFunc func(ctx context.Context, service, method string, in, out proto.Message) error
|
||||||
|
|
||||||
|
// WithAPICallFunc returns a copy of the parent context
|
||||||
|
// that will cause API calls to invoke f instead of their normal operation.
|
||||||
|
//
|
||||||
|
// This is intended for advanced users only.
|
||||||
|
func WithAPICallFunc(ctx context.Context, f APICallFunc) context.Context {
|
||||||
|
return internal.WithCallOverride(ctx, internal.CallOverrideFunc(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// APICall performs an API call.
|
||||||
|
//
|
||||||
|
// This is not intended for general use; it is exported for use in conjunction
|
||||||
|
// with WithAPICallFunc.
|
||||||
|
func APICall(ctx context.Context, service, method string, in, out proto.Message) error {
|
||||||
|
return internal.Call(ctx, service, method, in, out)
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackgroundContext returns a context not associated with a request.
|
||||||
|
// This should only be used when not servicing a request.
|
||||||
|
// This only works in App Engine "flexible environment".
|
||||||
|
func BackgroundContext() context.Context {
|
||||||
|
return internal.BackgroundContext()
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file provides error functions for common API failure modes.
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsOverQuota reports whether err represents an API call failure
|
||||||
|
// due to insufficient available quota.
|
||||||
|
func IsOverQuota(err error) bool {
|
||||||
|
callErr, ok := err.(*internal.CallError)
|
||||||
|
return ok && callErr.Code == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiError is returned by batch operations when there are errors with
|
||||||
|
// particular elements. Errors will be in a one-to-one correspondence with
|
||||||
|
// the input elements; successful elements will have a nil entry.
|
||||||
|
type MultiError []error
|
||||||
|
|
||||||
|
func (m MultiError) Error() string {
|
||||||
|
s, n := "", 0
|
||||||
|
for _, e := range m {
|
||||||
|
if e != nil {
|
||||||
|
if n == 0 {
|
||||||
|
s = e.Error()
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return "(0 errors)"
|
||||||
|
case 1:
|
||||||
|
return s
|
||||||
|
case 2:
|
||||||
|
return s + " (and 1 other error)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
pb "google.golang.org/appengine/internal/app_identity"
|
||||||
|
modpb "google.golang.org/appengine/internal/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppID returns the application ID for the current application.
|
||||||
|
// The string will be a plain application ID (e.g. "appid"), with a
|
||||||
|
// domain prefix for custom domain deployments (e.g. "example.com:appid").
|
||||||
|
func AppID(c context.Context) string { return internal.AppID(c) }
|
||||||
|
|
||||||
|
// DefaultVersionHostname returns the standard hostname of the default version
|
||||||
|
// of the current application (e.g. "my-app.appspot.com"). This is suitable for
|
||||||
|
// use in constructing URLs.
|
||||||
|
func DefaultVersionHostname(c context.Context) string {
|
||||||
|
return internal.DefaultVersionHostname(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleName returns the module name of the current instance.
|
||||||
|
func ModuleName(c context.Context) string {
|
||||||
|
return internal.ModuleName(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleHostname returns a hostname of a module instance.
|
||||||
|
// If module is the empty string, it refers to the module of the current instance.
|
||||||
|
// If version is empty, it refers to the version of the current instance if valid,
|
||||||
|
// or the default version of the module of the current instance.
|
||||||
|
// If instance is empty, ModuleHostname returns the load-balancing hostname.
|
||||||
|
func ModuleHostname(c context.Context, module, version, instance string) (string, error) {
|
||||||
|
req := &modpb.GetHostnameRequest{}
|
||||||
|
if module != "" {
|
||||||
|
req.Module = &module
|
||||||
|
}
|
||||||
|
if version != "" {
|
||||||
|
req.Version = &version
|
||||||
|
}
|
||||||
|
if instance != "" {
|
||||||
|
req.Instance = &instance
|
||||||
|
}
|
||||||
|
res := &modpb.GetHostnameResponse{}
|
||||||
|
if err := internal.Call(c, "modules", "GetHostname", req, res); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return *res.Hostname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionID returns the version ID for the current application.
|
||||||
|
// It will be of the form "X.Y", where X is specified in app.yaml,
|
||||||
|
// and Y is a number generated when each version of the app is uploaded.
|
||||||
|
// It does not include a module name.
|
||||||
|
func VersionID(c context.Context) string { return internal.VersionID(c) }
|
||||||
|
|
||||||
|
// InstanceID returns a mostly-unique identifier for this instance.
|
||||||
|
func InstanceID() string { return internal.InstanceID() }
|
||||||
|
|
||||||
|
// Datacenter returns an identifier for the datacenter that the instance is running in.
|
||||||
|
func Datacenter(c context.Context) string { return internal.Datacenter(c) }
|
||||||
|
|
||||||
|
// ServerSoftware returns the App Engine release version.
|
||||||
|
// In production, it looks like "Google App Engine/X.Y.Z".
|
||||||
|
// In the development appserver, it looks like "Development/X.Y".
|
||||||
|
func ServerSoftware() string { return internal.ServerSoftware() }
|
||||||
|
|
||||||
|
// RequestID returns a string that uniquely identifies the request.
|
||||||
|
func RequestID(c context.Context) string { return internal.RequestID(c) }
|
||||||
|
|
||||||
|
// AccessToken generates an OAuth2 access token for the specified scopes on
|
||||||
|
// behalf of service account of this application. This token will expire after
|
||||||
|
// the returned time.
|
||||||
|
func AccessToken(c context.Context, scopes ...string) (token string, expiry time.Time, err error) {
|
||||||
|
req := &pb.GetAccessTokenRequest{Scope: scopes}
|
||||||
|
res := &pb.GetAccessTokenResponse{}
|
||||||
|
|
||||||
|
err = internal.Call(c, "app_identity_service", "GetAccessToken", req, res)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
return res.GetAccessToken(), time.Unix(res.GetExpirationTime(), 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate represents a public certificate for the app.
|
||||||
|
type Certificate struct {
|
||||||
|
KeyName string
|
||||||
|
Data []byte // PEM-encoded X.509 certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicCertificates retrieves the public certificates for the app.
|
||||||
|
// They can be used to verify a signature returned by SignBytes.
|
||||||
|
func PublicCertificates(c context.Context) ([]Certificate, error) {
|
||||||
|
req := &pb.GetPublicCertificateForAppRequest{}
|
||||||
|
res := &pb.GetPublicCertificateForAppResponse{}
|
||||||
|
if err := internal.Call(c, "app_identity_service", "GetPublicCertificatesForApp", req, res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var cs []Certificate
|
||||||
|
for _, pc := range res.PublicCertificateList {
|
||||||
|
cs = append(cs, Certificate{
|
||||||
|
KeyName: pc.GetKeyName(),
|
||||||
|
Data: []byte(pc.GetX509CertificatePem()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceAccount returns a string representing the service account name, in
|
||||||
|
// the form of an email address (typically app_id@appspot.gserviceaccount.com).
|
||||||
|
func ServiceAccount(c context.Context) (string, error) {
|
||||||
|
req := &pb.GetServiceAccountNameRequest{}
|
||||||
|
res := &pb.GetServiceAccountNameResponse{}
|
||||||
|
|
||||||
|
err := internal.Call(c, "app_identity_service", "GetServiceAccountName", req, res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return res.GetServiceAccountName(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignBytes signs bytes using a private key unique to your application.
|
||||||
|
func SignBytes(c context.Context, bytes []byte) (keyName string, signature []byte, err error) {
|
||||||
|
req := &pb.SignForAppRequest{BytesToSign: bytes}
|
||||||
|
res := &pb.SignForAppResponse{}
|
||||||
|
|
||||||
|
if err := internal.Call(c, "app_identity_service", "SignForApp", req, res); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return res.GetKeyName(), res.GetSignatureBytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.RegisterErrorCodeMap("app_identity_service", pb.AppIdentityServiceError_ErrorCode_name)
|
||||||
|
internal.RegisterErrorCodeMap("modules", modpb.ModulesServiceError_ErrorCode_name)
|
||||||
|
}
|
296
vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go
generated
vendored
Normal file
296
vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
// Code generated by protoc-gen-go.
|
||||||
|
// source: google.golang.org/appengine/internal/app_identity/app_identity_service.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package app_identity is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
google.golang.org/appengine/internal/app_identity/app_identity_service.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
AppIdentityServiceError
|
||||||
|
SignForAppRequest
|
||||||
|
SignForAppResponse
|
||||||
|
GetPublicCertificateForAppRequest
|
||||||
|
PublicCertificate
|
||||||
|
GetPublicCertificateForAppResponse
|
||||||
|
GetServiceAccountNameRequest
|
||||||
|
GetServiceAccountNameResponse
|
||||||
|
GetAccessTokenRequest
|
||||||
|
GetAccessTokenResponse
|
||||||
|
GetDefaultGcsBucketNameRequest
|
||||||
|
GetDefaultGcsBucketNameResponse
|
||||||
|
*/
|
||||||
|
package app_identity
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
type AppIdentityServiceError_ErrorCode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppIdentityServiceError_SUCCESS AppIdentityServiceError_ErrorCode = 0
|
||||||
|
AppIdentityServiceError_UNKNOWN_SCOPE AppIdentityServiceError_ErrorCode = 9
|
||||||
|
AppIdentityServiceError_BLOB_TOO_LARGE AppIdentityServiceError_ErrorCode = 1000
|
||||||
|
AppIdentityServiceError_DEADLINE_EXCEEDED AppIdentityServiceError_ErrorCode = 1001
|
||||||
|
AppIdentityServiceError_NOT_A_VALID_APP AppIdentityServiceError_ErrorCode = 1002
|
||||||
|
AppIdentityServiceError_UNKNOWN_ERROR AppIdentityServiceError_ErrorCode = 1003
|
||||||
|
AppIdentityServiceError_NOT_ALLOWED AppIdentityServiceError_ErrorCode = 1005
|
||||||
|
AppIdentityServiceError_NOT_IMPLEMENTED AppIdentityServiceError_ErrorCode = 1006
|
||||||
|
)
|
||||||
|
|
||||||
|
var AppIdentityServiceError_ErrorCode_name = map[int32]string{
|
||||||
|
0: "SUCCESS",
|
||||||
|
9: "UNKNOWN_SCOPE",
|
||||||
|
1000: "BLOB_TOO_LARGE",
|
||||||
|
1001: "DEADLINE_EXCEEDED",
|
||||||
|
1002: "NOT_A_VALID_APP",
|
||||||
|
1003: "UNKNOWN_ERROR",
|
||||||
|
1005: "NOT_ALLOWED",
|
||||||
|
1006: "NOT_IMPLEMENTED",
|
||||||
|
}
|
||||||
|
var AppIdentityServiceError_ErrorCode_value = map[string]int32{
|
||||||
|
"SUCCESS": 0,
|
||||||
|
"UNKNOWN_SCOPE": 9,
|
||||||
|
"BLOB_TOO_LARGE": 1000,
|
||||||
|
"DEADLINE_EXCEEDED": 1001,
|
||||||
|
"NOT_A_VALID_APP": 1002,
|
||||||
|
"UNKNOWN_ERROR": 1003,
|
||||||
|
"NOT_ALLOWED": 1005,
|
||||||
|
"NOT_IMPLEMENTED": 1006,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x AppIdentityServiceError_ErrorCode) Enum() *AppIdentityServiceError_ErrorCode {
|
||||||
|
p := new(AppIdentityServiceError_ErrorCode)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
func (x AppIdentityServiceError_ErrorCode) String() string {
|
||||||
|
return proto.EnumName(AppIdentityServiceError_ErrorCode_name, int32(x))
|
||||||
|
}
|
||||||
|
func (x *AppIdentityServiceError_ErrorCode) UnmarshalJSON(data []byte) error {
|
||||||
|
value, err := proto.UnmarshalJSONEnum(AppIdentityServiceError_ErrorCode_value, data, "AppIdentityServiceError_ErrorCode")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*x = AppIdentityServiceError_ErrorCode(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppIdentityServiceError struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AppIdentityServiceError) Reset() { *m = AppIdentityServiceError{} }
|
||||||
|
func (m *AppIdentityServiceError) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*AppIdentityServiceError) ProtoMessage() {}
|
||||||
|
|
||||||
|
type SignForAppRequest struct {
|
||||||
|
BytesToSign []byte `protobuf:"bytes,1,opt,name=bytes_to_sign" json:"bytes_to_sign,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SignForAppRequest) Reset() { *m = SignForAppRequest{} }
|
||||||
|
func (m *SignForAppRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SignForAppRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *SignForAppRequest) GetBytesToSign() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.BytesToSign
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignForAppResponse struct {
|
||||||
|
KeyName *string `protobuf:"bytes,1,opt,name=key_name" json:"key_name,omitempty"`
|
||||||
|
SignatureBytes []byte `protobuf:"bytes,2,opt,name=signature_bytes" json:"signature_bytes,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SignForAppResponse) Reset() { *m = SignForAppResponse{} }
|
||||||
|
func (m *SignForAppResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SignForAppResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *SignForAppResponse) GetKeyName() string {
|
||||||
|
if m != nil && m.KeyName != nil {
|
||||||
|
return *m.KeyName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SignForAppResponse) GetSignatureBytes() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.SignatureBytes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPublicCertificateForAppRequest struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPublicCertificateForAppRequest) Reset() { *m = GetPublicCertificateForAppRequest{} }
|
||||||
|
func (m *GetPublicCertificateForAppRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetPublicCertificateForAppRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
type PublicCertificate struct {
|
||||||
|
KeyName *string `protobuf:"bytes,1,opt,name=key_name" json:"key_name,omitempty"`
|
||||||
|
X509CertificatePem *string `protobuf:"bytes,2,opt,name=x509_certificate_pem" json:"x509_certificate_pem,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PublicCertificate) Reset() { *m = PublicCertificate{} }
|
||||||
|
func (m *PublicCertificate) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*PublicCertificate) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *PublicCertificate) GetKeyName() string {
|
||||||
|
if m != nil && m.KeyName != nil {
|
||||||
|
return *m.KeyName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PublicCertificate) GetX509CertificatePem() string {
|
||||||
|
if m != nil && m.X509CertificatePem != nil {
|
||||||
|
return *m.X509CertificatePem
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPublicCertificateForAppResponse struct {
|
||||||
|
PublicCertificateList []*PublicCertificate `protobuf:"bytes,1,rep,name=public_certificate_list" json:"public_certificate_list,omitempty"`
|
||||||
|
MaxClientCacheTimeInSecond *int64 `protobuf:"varint,2,opt,name=max_client_cache_time_in_second" json:"max_client_cache_time_in_second,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPublicCertificateForAppResponse) Reset() { *m = GetPublicCertificateForAppResponse{} }
|
||||||
|
func (m *GetPublicCertificateForAppResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetPublicCertificateForAppResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetPublicCertificateForAppResponse) GetPublicCertificateList() []*PublicCertificate {
|
||||||
|
if m != nil {
|
||||||
|
return m.PublicCertificateList
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPublicCertificateForAppResponse) GetMaxClientCacheTimeInSecond() int64 {
|
||||||
|
if m != nil && m.MaxClientCacheTimeInSecond != nil {
|
||||||
|
return *m.MaxClientCacheTimeInSecond
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetServiceAccountNameRequest struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetServiceAccountNameRequest) Reset() { *m = GetServiceAccountNameRequest{} }
|
||||||
|
func (m *GetServiceAccountNameRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetServiceAccountNameRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
type GetServiceAccountNameResponse struct {
|
||||||
|
ServiceAccountName *string `protobuf:"bytes,1,opt,name=service_account_name" json:"service_account_name,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetServiceAccountNameResponse) Reset() { *m = GetServiceAccountNameResponse{} }
|
||||||
|
func (m *GetServiceAccountNameResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetServiceAccountNameResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetServiceAccountNameResponse) GetServiceAccountName() string {
|
||||||
|
if m != nil && m.ServiceAccountName != nil {
|
||||||
|
return *m.ServiceAccountName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAccessTokenRequest struct {
|
||||||
|
Scope []string `protobuf:"bytes,1,rep,name=scope" json:"scope,omitempty"`
|
||||||
|
ServiceAccountId *int64 `protobuf:"varint,2,opt,name=service_account_id" json:"service_account_id,omitempty"`
|
||||||
|
ServiceAccountName *string `protobuf:"bytes,3,opt,name=service_account_name" json:"service_account_name,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenRequest) Reset() { *m = GetAccessTokenRequest{} }
|
||||||
|
func (m *GetAccessTokenRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetAccessTokenRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenRequest) GetScope() []string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Scope
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenRequest) GetServiceAccountId() int64 {
|
||||||
|
if m != nil && m.ServiceAccountId != nil {
|
||||||
|
return *m.ServiceAccountId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenRequest) GetServiceAccountName() string {
|
||||||
|
if m != nil && m.ServiceAccountName != nil {
|
||||||
|
return *m.ServiceAccountName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAccessTokenResponse struct {
|
||||||
|
AccessToken *string `protobuf:"bytes,1,opt,name=access_token" json:"access_token,omitempty"`
|
||||||
|
ExpirationTime *int64 `protobuf:"varint,2,opt,name=expiration_time" json:"expiration_time,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenResponse) Reset() { *m = GetAccessTokenResponse{} }
|
||||||
|
func (m *GetAccessTokenResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetAccessTokenResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenResponse) GetAccessToken() string {
|
||||||
|
if m != nil && m.AccessToken != nil {
|
||||||
|
return *m.AccessToken
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetAccessTokenResponse) GetExpirationTime() int64 {
|
||||||
|
if m != nil && m.ExpirationTime != nil {
|
||||||
|
return *m.ExpirationTime
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDefaultGcsBucketNameRequest struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetDefaultGcsBucketNameRequest) Reset() { *m = GetDefaultGcsBucketNameRequest{} }
|
||||||
|
func (m *GetDefaultGcsBucketNameRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetDefaultGcsBucketNameRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
type GetDefaultGcsBucketNameResponse struct {
|
||||||
|
DefaultGcsBucketName *string `protobuf:"bytes,1,opt,name=default_gcs_bucket_name" json:"default_gcs_bucket_name,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetDefaultGcsBucketNameResponse) Reset() { *m = GetDefaultGcsBucketNameResponse{} }
|
||||||
|
func (m *GetDefaultGcsBucketNameResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetDefaultGcsBucketNameResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetDefaultGcsBucketNameResponse) GetDefaultGcsBucketName() string {
|
||||||
|
if m != nil && m.DefaultGcsBucketName != nil {
|
||||||
|
return *m.DefaultGcsBucketName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
64
vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.proto
generated
vendored
Normal file
64
vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.proto
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
option go_package = "app_identity";
|
||||||
|
|
||||||
|
package appengine;
|
||||||
|
|
||||||
|
message AppIdentityServiceError {
|
||||||
|
enum ErrorCode {
|
||||||
|
SUCCESS = 0;
|
||||||
|
UNKNOWN_SCOPE = 9;
|
||||||
|
BLOB_TOO_LARGE = 1000;
|
||||||
|
DEADLINE_EXCEEDED = 1001;
|
||||||
|
NOT_A_VALID_APP = 1002;
|
||||||
|
UNKNOWN_ERROR = 1003;
|
||||||
|
NOT_ALLOWED = 1005;
|
||||||
|
NOT_IMPLEMENTED = 1006;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignForAppRequest {
|
||||||
|
optional bytes bytes_to_sign = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignForAppResponse {
|
||||||
|
optional string key_name = 1;
|
||||||
|
optional bytes signature_bytes = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPublicCertificateForAppRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message PublicCertificate {
|
||||||
|
optional string key_name = 1;
|
||||||
|
optional string x509_certificate_pem = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPublicCertificateForAppResponse {
|
||||||
|
repeated PublicCertificate public_certificate_list = 1;
|
||||||
|
optional int64 max_client_cache_time_in_second = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetServiceAccountNameRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetServiceAccountNameResponse {
|
||||||
|
optional string service_account_name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAccessTokenRequest {
|
||||||
|
repeated string scope = 1;
|
||||||
|
optional int64 service_account_id = 2;
|
||||||
|
optional string service_account_name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAccessTokenResponse {
|
||||||
|
optional string access_token = 1;
|
||||||
|
optional int64 expiration_time = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDefaultGcsBucketNameRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDefaultGcsBucketNameResponse {
|
||||||
|
optional string default_gcs_bucket_name = 1;
|
||||||
|
}
|
375
vendor/google.golang.org/appengine/internal/modules/modules_service.pb.go
generated
vendored
Normal file
375
vendor/google.golang.org/appengine/internal/modules/modules_service.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
// Code generated by protoc-gen-go.
|
||||||
|
// source: google.golang.org/appengine/internal/modules/modules_service.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package modules is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
google.golang.org/appengine/internal/modules/modules_service.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
ModulesServiceError
|
||||||
|
GetModulesRequest
|
||||||
|
GetModulesResponse
|
||||||
|
GetVersionsRequest
|
||||||
|
GetVersionsResponse
|
||||||
|
GetDefaultVersionRequest
|
||||||
|
GetDefaultVersionResponse
|
||||||
|
GetNumInstancesRequest
|
||||||
|
GetNumInstancesResponse
|
||||||
|
SetNumInstancesRequest
|
||||||
|
SetNumInstancesResponse
|
||||||
|
StartModuleRequest
|
||||||
|
StartModuleResponse
|
||||||
|
StopModuleRequest
|
||||||
|
StopModuleResponse
|
||||||
|
GetHostnameRequest
|
||||||
|
GetHostnameResponse
|
||||||
|
*/
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
type ModulesServiceError_ErrorCode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModulesServiceError_OK ModulesServiceError_ErrorCode = 0
|
||||||
|
ModulesServiceError_INVALID_MODULE ModulesServiceError_ErrorCode = 1
|
||||||
|
ModulesServiceError_INVALID_VERSION ModulesServiceError_ErrorCode = 2
|
||||||
|
ModulesServiceError_INVALID_INSTANCES ModulesServiceError_ErrorCode = 3
|
||||||
|
ModulesServiceError_TRANSIENT_ERROR ModulesServiceError_ErrorCode = 4
|
||||||
|
ModulesServiceError_UNEXPECTED_STATE ModulesServiceError_ErrorCode = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
var ModulesServiceError_ErrorCode_name = map[int32]string{
|
||||||
|
0: "OK",
|
||||||
|
1: "INVALID_MODULE",
|
||||||
|
2: "INVALID_VERSION",
|
||||||
|
3: "INVALID_INSTANCES",
|
||||||
|
4: "TRANSIENT_ERROR",
|
||||||
|
5: "UNEXPECTED_STATE",
|
||||||
|
}
|
||||||
|
var ModulesServiceError_ErrorCode_value = map[string]int32{
|
||||||
|
"OK": 0,
|
||||||
|
"INVALID_MODULE": 1,
|
||||||
|
"INVALID_VERSION": 2,
|
||||||
|
"INVALID_INSTANCES": 3,
|
||||||
|
"TRANSIENT_ERROR": 4,
|
||||||
|
"UNEXPECTED_STATE": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ModulesServiceError_ErrorCode) Enum() *ModulesServiceError_ErrorCode {
|
||||||
|
p := new(ModulesServiceError_ErrorCode)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
func (x ModulesServiceError_ErrorCode) String() string {
|
||||||
|
return proto.EnumName(ModulesServiceError_ErrorCode_name, int32(x))
|
||||||
|
}
|
||||||
|
func (x *ModulesServiceError_ErrorCode) UnmarshalJSON(data []byte) error {
|
||||||
|
value, err := proto.UnmarshalJSONEnum(ModulesServiceError_ErrorCode_value, data, "ModulesServiceError_ErrorCode")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*x = ModulesServiceError_ErrorCode(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModulesServiceError struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModulesServiceError) Reset() { *m = ModulesServiceError{} }
|
||||||
|
func (m *ModulesServiceError) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*ModulesServiceError) ProtoMessage() {}
|
||||||
|
|
||||||
|
type GetModulesRequest struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetModulesRequest) Reset() { *m = GetModulesRequest{} }
|
||||||
|
func (m *GetModulesRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetModulesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
type GetModulesResponse struct {
|
||||||
|
Module []string `protobuf:"bytes,1,rep,name=module" json:"module,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetModulesResponse) Reset() { *m = GetModulesResponse{} }
|
||||||
|
func (m *GetModulesResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetModulesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetModulesResponse) GetModule() []string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Module
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetVersionsRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetVersionsRequest) Reset() { *m = GetVersionsRequest{} }
|
||||||
|
func (m *GetVersionsRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetVersionsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetVersionsRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetVersionsResponse struct {
|
||||||
|
Version []string `protobuf:"bytes,1,rep,name=version" json:"version,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetVersionsResponse) Reset() { *m = GetVersionsResponse{} }
|
||||||
|
func (m *GetVersionsResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetVersionsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetVersionsResponse) GetVersion() []string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Version
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDefaultVersionRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetDefaultVersionRequest) Reset() { *m = GetDefaultVersionRequest{} }
|
||||||
|
func (m *GetDefaultVersionRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetDefaultVersionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetDefaultVersionRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDefaultVersionResponse struct {
|
||||||
|
Version *string `protobuf:"bytes,1,req,name=version" json:"version,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetDefaultVersionResponse) Reset() { *m = GetDefaultVersionResponse{} }
|
||||||
|
func (m *GetDefaultVersionResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetDefaultVersionResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetDefaultVersionResponse) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNumInstancesRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
Version *string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetNumInstancesRequest) Reset() { *m = GetNumInstancesRequest{} }
|
||||||
|
func (m *GetNumInstancesRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetNumInstancesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetNumInstancesRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetNumInstancesRequest) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNumInstancesResponse struct {
|
||||||
|
Instances *int64 `protobuf:"varint,1,req,name=instances" json:"instances,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetNumInstancesResponse) Reset() { *m = GetNumInstancesResponse{} }
|
||||||
|
func (m *GetNumInstancesResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetNumInstancesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetNumInstancesResponse) GetInstances() int64 {
|
||||||
|
if m != nil && m.Instances != nil {
|
||||||
|
return *m.Instances
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetNumInstancesRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
Version *string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
|
||||||
|
Instances *int64 `protobuf:"varint,3,req,name=instances" json:"instances,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SetNumInstancesRequest) Reset() { *m = SetNumInstancesRequest{} }
|
||||||
|
func (m *SetNumInstancesRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SetNumInstancesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *SetNumInstancesRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SetNumInstancesRequest) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SetNumInstancesRequest) GetInstances() int64 {
|
||||||
|
if m != nil && m.Instances != nil {
|
||||||
|
return *m.Instances
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetNumInstancesResponse struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SetNumInstancesResponse) Reset() { *m = SetNumInstancesResponse{} }
|
||||||
|
func (m *SetNumInstancesResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SetNumInstancesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
type StartModuleRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,req,name=module" json:"module,omitempty"`
|
||||||
|
Version *string `protobuf:"bytes,2,req,name=version" json:"version,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StartModuleRequest) Reset() { *m = StartModuleRequest{} }
|
||||||
|
func (m *StartModuleRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*StartModuleRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *StartModuleRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StartModuleRequest) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartModuleResponse struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StartModuleResponse) Reset() { *m = StartModuleResponse{} }
|
||||||
|
func (m *StartModuleResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*StartModuleResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
type StopModuleRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
Version *string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StopModuleRequest) Reset() { *m = StopModuleRequest{} }
|
||||||
|
func (m *StopModuleRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*StopModuleRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *StopModuleRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StopModuleRequest) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StopModuleResponse struct {
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StopModuleResponse) Reset() { *m = StopModuleResponse{} }
|
||||||
|
func (m *StopModuleResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*StopModuleResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
type GetHostnameRequest struct {
|
||||||
|
Module *string `protobuf:"bytes,1,opt,name=module" json:"module,omitempty"`
|
||||||
|
Version *string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
|
||||||
|
Instance *string `protobuf:"bytes,3,opt,name=instance" json:"instance,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetHostnameRequest) Reset() { *m = GetHostnameRequest{} }
|
||||||
|
func (m *GetHostnameRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetHostnameRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetHostnameRequest) GetModule() string {
|
||||||
|
if m != nil && m.Module != nil {
|
||||||
|
return *m.Module
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetHostnameRequest) GetVersion() string {
|
||||||
|
if m != nil && m.Version != nil {
|
||||||
|
return *m.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetHostnameRequest) GetInstance() string {
|
||||||
|
if m != nil && m.Instance != nil {
|
||||||
|
return *m.Instance
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetHostnameResponse struct {
|
||||||
|
Hostname *string `protobuf:"bytes,1,req,name=hostname" json:"hostname,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetHostnameResponse) Reset() { *m = GetHostnameResponse{} }
|
||||||
|
func (m *GetHostnameResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetHostnameResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *GetHostnameResponse) GetHostname() string {
|
||||||
|
if m != nil && m.Hostname != nil {
|
||||||
|
return *m.Hostname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
80
vendor/google.golang.org/appengine/internal/modules/modules_service.proto
generated
vendored
Normal file
80
vendor/google.golang.org/appengine/internal/modules/modules_service.proto
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
option go_package = "modules";
|
||||||
|
|
||||||
|
package appengine;
|
||||||
|
|
||||||
|
message ModulesServiceError {
|
||||||
|
enum ErrorCode {
|
||||||
|
OK = 0;
|
||||||
|
INVALID_MODULE = 1;
|
||||||
|
INVALID_VERSION = 2;
|
||||||
|
INVALID_INSTANCES = 3;
|
||||||
|
TRANSIENT_ERROR = 4;
|
||||||
|
UNEXPECTED_STATE = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetModulesRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetModulesResponse {
|
||||||
|
repeated string module = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetVersionsRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetVersionsResponse {
|
||||||
|
repeated string version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDefaultVersionRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDefaultVersionResponse {
|
||||||
|
required string version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetNumInstancesRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
optional string version = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetNumInstancesResponse {
|
||||||
|
required int64 instances = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetNumInstancesRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
optional string version = 2;
|
||||||
|
required int64 instances = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetNumInstancesResponse {}
|
||||||
|
|
||||||
|
message StartModuleRequest {
|
||||||
|
required string module = 1;
|
||||||
|
required string version = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartModuleResponse {}
|
||||||
|
|
||||||
|
message StopModuleRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
optional string version = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopModuleResponse {}
|
||||||
|
|
||||||
|
message GetHostnameRequest {
|
||||||
|
optional string module = 1;
|
||||||
|
optional string version = 2;
|
||||||
|
optional string instance = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetHostnameResponse {
|
||||||
|
required string hostname = 1;
|
||||||
|
}
|
||||||
|
|
1858
vendor/google.golang.org/appengine/internal/socket/socket_service.pb.go
generated
vendored
Normal file
1858
vendor/google.golang.org/appengine/internal/socket/socket_service.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
460
vendor/google.golang.org/appengine/internal/socket/socket_service.proto
generated
vendored
Normal file
460
vendor/google.golang.org/appengine/internal/socket/socket_service.proto
generated
vendored
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
option go_package = "socket";
|
||||||
|
|
||||||
|
package appengine;
|
||||||
|
|
||||||
|
message RemoteSocketServiceError {
|
||||||
|
enum ErrorCode {
|
||||||
|
SYSTEM_ERROR = 1;
|
||||||
|
GAI_ERROR = 2;
|
||||||
|
FAILURE = 4;
|
||||||
|
PERMISSION_DENIED = 5;
|
||||||
|
INVALID_REQUEST = 6;
|
||||||
|
SOCKET_CLOSED = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SystemError {
|
||||||
|
option allow_alias = true;
|
||||||
|
|
||||||
|
SYS_SUCCESS = 0;
|
||||||
|
SYS_EPERM = 1;
|
||||||
|
SYS_ENOENT = 2;
|
||||||
|
SYS_ESRCH = 3;
|
||||||
|
SYS_EINTR = 4;
|
||||||
|
SYS_EIO = 5;
|
||||||
|
SYS_ENXIO = 6;
|
||||||
|
SYS_E2BIG = 7;
|
||||||
|
SYS_ENOEXEC = 8;
|
||||||
|
SYS_EBADF = 9;
|
||||||
|
SYS_ECHILD = 10;
|
||||||
|
SYS_EAGAIN = 11;
|
||||||
|
SYS_EWOULDBLOCK = 11;
|
||||||
|
SYS_ENOMEM = 12;
|
||||||
|
SYS_EACCES = 13;
|
||||||
|
SYS_EFAULT = 14;
|
||||||
|
SYS_ENOTBLK = 15;
|
||||||
|
SYS_EBUSY = 16;
|
||||||
|
SYS_EEXIST = 17;
|
||||||
|
SYS_EXDEV = 18;
|
||||||
|
SYS_ENODEV = 19;
|
||||||
|
SYS_ENOTDIR = 20;
|
||||||
|
SYS_EISDIR = 21;
|
||||||
|
SYS_EINVAL = 22;
|
||||||
|
SYS_ENFILE = 23;
|
||||||
|
SYS_EMFILE = 24;
|
||||||
|
SYS_ENOTTY = 25;
|
||||||
|
SYS_ETXTBSY = 26;
|
||||||
|
SYS_EFBIG = 27;
|
||||||
|
SYS_ENOSPC = 28;
|
||||||
|
SYS_ESPIPE = 29;
|
||||||
|
SYS_EROFS = 30;
|
||||||
|
SYS_EMLINK = 31;
|
||||||
|
SYS_EPIPE = 32;
|
||||||
|
SYS_EDOM = 33;
|
||||||
|
SYS_ERANGE = 34;
|
||||||
|
SYS_EDEADLK = 35;
|
||||||
|
SYS_EDEADLOCK = 35;
|
||||||
|
SYS_ENAMETOOLONG = 36;
|
||||||
|
SYS_ENOLCK = 37;
|
||||||
|
SYS_ENOSYS = 38;
|
||||||
|
SYS_ENOTEMPTY = 39;
|
||||||
|
SYS_ELOOP = 40;
|
||||||
|
SYS_ENOMSG = 42;
|
||||||
|
SYS_EIDRM = 43;
|
||||||
|
SYS_ECHRNG = 44;
|
||||||
|
SYS_EL2NSYNC = 45;
|
||||||
|
SYS_EL3HLT = 46;
|
||||||
|
SYS_EL3RST = 47;
|
||||||
|
SYS_ELNRNG = 48;
|
||||||
|
SYS_EUNATCH = 49;
|
||||||
|
SYS_ENOCSI = 50;
|
||||||
|
SYS_EL2HLT = 51;
|
||||||
|
SYS_EBADE = 52;
|
||||||
|
SYS_EBADR = 53;
|
||||||
|
SYS_EXFULL = 54;
|
||||||
|
SYS_ENOANO = 55;
|
||||||
|
SYS_EBADRQC = 56;
|
||||||
|
SYS_EBADSLT = 57;
|
||||||
|
SYS_EBFONT = 59;
|
||||||
|
SYS_ENOSTR = 60;
|
||||||
|
SYS_ENODATA = 61;
|
||||||
|
SYS_ETIME = 62;
|
||||||
|
SYS_ENOSR = 63;
|
||||||
|
SYS_ENONET = 64;
|
||||||
|
SYS_ENOPKG = 65;
|
||||||
|
SYS_EREMOTE = 66;
|
||||||
|
SYS_ENOLINK = 67;
|
||||||
|
SYS_EADV = 68;
|
||||||
|
SYS_ESRMNT = 69;
|
||||||
|
SYS_ECOMM = 70;
|
||||||
|
SYS_EPROTO = 71;
|
||||||
|
SYS_EMULTIHOP = 72;
|
||||||
|
SYS_EDOTDOT = 73;
|
||||||
|
SYS_EBADMSG = 74;
|
||||||
|
SYS_EOVERFLOW = 75;
|
||||||
|
SYS_ENOTUNIQ = 76;
|
||||||
|
SYS_EBADFD = 77;
|
||||||
|
SYS_EREMCHG = 78;
|
||||||
|
SYS_ELIBACC = 79;
|
||||||
|
SYS_ELIBBAD = 80;
|
||||||
|
SYS_ELIBSCN = 81;
|
||||||
|
SYS_ELIBMAX = 82;
|
||||||
|
SYS_ELIBEXEC = 83;
|
||||||
|
SYS_EILSEQ = 84;
|
||||||
|
SYS_ERESTART = 85;
|
||||||
|
SYS_ESTRPIPE = 86;
|
||||||
|
SYS_EUSERS = 87;
|
||||||
|
SYS_ENOTSOCK = 88;
|
||||||
|
SYS_EDESTADDRREQ = 89;
|
||||||
|
SYS_EMSGSIZE = 90;
|
||||||
|
SYS_EPROTOTYPE = 91;
|
||||||
|
SYS_ENOPROTOOPT = 92;
|
||||||
|
SYS_EPROTONOSUPPORT = 93;
|
||||||
|
SYS_ESOCKTNOSUPPORT = 94;
|
||||||
|
SYS_EOPNOTSUPP = 95;
|
||||||
|
SYS_ENOTSUP = 95;
|
||||||
|
SYS_EPFNOSUPPORT = 96;
|
||||||
|
SYS_EAFNOSUPPORT = 97;
|
||||||
|
SYS_EADDRINUSE = 98;
|
||||||
|
SYS_EADDRNOTAVAIL = 99;
|
||||||
|
SYS_ENETDOWN = 100;
|
||||||
|
SYS_ENETUNREACH = 101;
|
||||||
|
SYS_ENETRESET = 102;
|
||||||
|
SYS_ECONNABORTED = 103;
|
||||||
|
SYS_ECONNRESET = 104;
|
||||||
|
SYS_ENOBUFS = 105;
|
||||||
|
SYS_EISCONN = 106;
|
||||||
|
SYS_ENOTCONN = 107;
|
||||||
|
SYS_ESHUTDOWN = 108;
|
||||||
|
SYS_ETOOMANYREFS = 109;
|
||||||
|
SYS_ETIMEDOUT = 110;
|
||||||
|
SYS_ECONNREFUSED = 111;
|
||||||
|
SYS_EHOSTDOWN = 112;
|
||||||
|
SYS_EHOSTUNREACH = 113;
|
||||||
|
SYS_EALREADY = 114;
|
||||||
|
SYS_EINPROGRESS = 115;
|
||||||
|
SYS_ESTALE = 116;
|
||||||
|
SYS_EUCLEAN = 117;
|
||||||
|
SYS_ENOTNAM = 118;
|
||||||
|
SYS_ENAVAIL = 119;
|
||||||
|
SYS_EISNAM = 120;
|
||||||
|
SYS_EREMOTEIO = 121;
|
||||||
|
SYS_EDQUOT = 122;
|
||||||
|
SYS_ENOMEDIUM = 123;
|
||||||
|
SYS_EMEDIUMTYPE = 124;
|
||||||
|
SYS_ECANCELED = 125;
|
||||||
|
SYS_ENOKEY = 126;
|
||||||
|
SYS_EKEYEXPIRED = 127;
|
||||||
|
SYS_EKEYREVOKED = 128;
|
||||||
|
SYS_EKEYREJECTED = 129;
|
||||||
|
SYS_EOWNERDEAD = 130;
|
||||||
|
SYS_ENOTRECOVERABLE = 131;
|
||||||
|
SYS_ERFKILL = 132;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional int32 system_error = 1 [default=0];
|
||||||
|
optional string error_detail = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddressPort {
|
||||||
|
required int32 port = 1;
|
||||||
|
optional bytes packed_address = 2;
|
||||||
|
|
||||||
|
optional string hostname_hint = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message CreateSocketRequest {
|
||||||
|
enum SocketFamily {
|
||||||
|
IPv4 = 1;
|
||||||
|
IPv6 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SocketProtocol {
|
||||||
|
TCP = 1;
|
||||||
|
UDP = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
required SocketFamily family = 1;
|
||||||
|
required SocketProtocol protocol = 2;
|
||||||
|
|
||||||
|
repeated SocketOption socket_options = 3;
|
||||||
|
|
||||||
|
optional AddressPort proxy_external_ip = 4;
|
||||||
|
|
||||||
|
optional int32 listen_backlog = 5 [default=0];
|
||||||
|
|
||||||
|
optional AddressPort remote_ip = 6;
|
||||||
|
|
||||||
|
optional string app_id = 9;
|
||||||
|
|
||||||
|
optional int64 project_id = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateSocketReply {
|
||||||
|
optional string socket_descriptor = 1;
|
||||||
|
|
||||||
|
optional AddressPort server_address = 3;
|
||||||
|
|
||||||
|
optional AddressPort proxy_external_ip = 4;
|
||||||
|
|
||||||
|
extensions 1000 to max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message BindRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required AddressPort proxy_external_ip = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BindReply {
|
||||||
|
optional AddressPort proxy_external_ip = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message GetSocketNameRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSocketNameReply {
|
||||||
|
optional AddressPort proxy_external_ip = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message GetPeerNameRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPeerNameReply {
|
||||||
|
optional AddressPort peer_ip = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message SocketOption {
|
||||||
|
|
||||||
|
enum SocketOptionLevel {
|
||||||
|
SOCKET_SOL_IP = 0;
|
||||||
|
SOCKET_SOL_SOCKET = 1;
|
||||||
|
SOCKET_SOL_TCP = 6;
|
||||||
|
SOCKET_SOL_UDP = 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SocketOptionName {
|
||||||
|
option allow_alias = true;
|
||||||
|
|
||||||
|
SOCKET_SO_DEBUG = 1;
|
||||||
|
SOCKET_SO_REUSEADDR = 2;
|
||||||
|
SOCKET_SO_TYPE = 3;
|
||||||
|
SOCKET_SO_ERROR = 4;
|
||||||
|
SOCKET_SO_DONTROUTE = 5;
|
||||||
|
SOCKET_SO_BROADCAST = 6;
|
||||||
|
SOCKET_SO_SNDBUF = 7;
|
||||||
|
SOCKET_SO_RCVBUF = 8;
|
||||||
|
SOCKET_SO_KEEPALIVE = 9;
|
||||||
|
SOCKET_SO_OOBINLINE = 10;
|
||||||
|
SOCKET_SO_LINGER = 13;
|
||||||
|
SOCKET_SO_RCVTIMEO = 20;
|
||||||
|
SOCKET_SO_SNDTIMEO = 21;
|
||||||
|
|
||||||
|
SOCKET_IP_TOS = 1;
|
||||||
|
SOCKET_IP_TTL = 2;
|
||||||
|
SOCKET_IP_HDRINCL = 3;
|
||||||
|
SOCKET_IP_OPTIONS = 4;
|
||||||
|
|
||||||
|
SOCKET_TCP_NODELAY = 1;
|
||||||
|
SOCKET_TCP_MAXSEG = 2;
|
||||||
|
SOCKET_TCP_CORK = 3;
|
||||||
|
SOCKET_TCP_KEEPIDLE = 4;
|
||||||
|
SOCKET_TCP_KEEPINTVL = 5;
|
||||||
|
SOCKET_TCP_KEEPCNT = 6;
|
||||||
|
SOCKET_TCP_SYNCNT = 7;
|
||||||
|
SOCKET_TCP_LINGER2 = 8;
|
||||||
|
SOCKET_TCP_DEFER_ACCEPT = 9;
|
||||||
|
SOCKET_TCP_WINDOW_CLAMP = 10;
|
||||||
|
SOCKET_TCP_INFO = 11;
|
||||||
|
SOCKET_TCP_QUICKACK = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
required SocketOptionLevel level = 1;
|
||||||
|
required SocketOptionName option = 2;
|
||||||
|
required bytes value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message SetSocketOptionsRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
repeated SocketOption options = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetSocketOptionsReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSocketOptionsRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
repeated SocketOption options = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSocketOptionsReply {
|
||||||
|
repeated SocketOption options = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ConnectRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required AddressPort remote_ip = 2;
|
||||||
|
optional double timeout_seconds = 3 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectReply {
|
||||||
|
optional AddressPort proxy_external_ip = 1;
|
||||||
|
|
||||||
|
extensions 1000 to max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ListenRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required int32 backlog = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListenReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message AcceptRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
optional double timeout_seconds = 2 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AcceptReply {
|
||||||
|
optional bytes new_socket_descriptor = 2;
|
||||||
|
optional AddressPort remote_address = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message ShutDownRequest {
|
||||||
|
enum How {
|
||||||
|
SOCKET_SHUT_RD = 1;
|
||||||
|
SOCKET_SHUT_WR = 2;
|
||||||
|
SOCKET_SHUT_RDWR = 3;
|
||||||
|
}
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required How how = 2;
|
||||||
|
required int64 send_offset = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ShutDownReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message CloseRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
optional int64 send_offset = 2 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message CloseReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message SendRequest {
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required bytes data = 2 [ctype=CORD];
|
||||||
|
required int64 stream_offset = 3;
|
||||||
|
optional int32 flags = 4 [default=0];
|
||||||
|
optional AddressPort send_to = 5;
|
||||||
|
optional double timeout_seconds = 6 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendReply {
|
||||||
|
optional int32 data_sent = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ReceiveRequest {
|
||||||
|
enum Flags {
|
||||||
|
MSG_OOB = 1;
|
||||||
|
MSG_PEEK = 2;
|
||||||
|
}
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required int32 data_size = 2;
|
||||||
|
optional int32 flags = 3 [default=0];
|
||||||
|
optional double timeout_seconds = 5 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReceiveReply {
|
||||||
|
optional int64 stream_offset = 2;
|
||||||
|
optional bytes data = 3 [ctype=CORD];
|
||||||
|
optional AddressPort received_from = 4;
|
||||||
|
optional int32 buffer_size = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message PollEvent {
|
||||||
|
|
||||||
|
enum PollEventFlag {
|
||||||
|
SOCKET_POLLNONE = 0;
|
||||||
|
SOCKET_POLLIN = 1;
|
||||||
|
SOCKET_POLLPRI = 2;
|
||||||
|
SOCKET_POLLOUT = 4;
|
||||||
|
SOCKET_POLLERR = 8;
|
||||||
|
SOCKET_POLLHUP = 16;
|
||||||
|
SOCKET_POLLNVAL = 32;
|
||||||
|
SOCKET_POLLRDNORM = 64;
|
||||||
|
SOCKET_POLLRDBAND = 128;
|
||||||
|
SOCKET_POLLWRNORM = 256;
|
||||||
|
SOCKET_POLLWRBAND = 512;
|
||||||
|
SOCKET_POLLMSG = 1024;
|
||||||
|
SOCKET_POLLREMOVE = 4096;
|
||||||
|
SOCKET_POLLRDHUP = 8192;
|
||||||
|
};
|
||||||
|
|
||||||
|
required string socket_descriptor = 1;
|
||||||
|
required int32 requested_events = 2;
|
||||||
|
required int32 observed_events = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PollRequest {
|
||||||
|
repeated PollEvent events = 1;
|
||||||
|
optional double timeout_seconds = 2 [default=-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message PollReply {
|
||||||
|
repeated PollEvent events = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveRequest {
|
||||||
|
required string name = 1;
|
||||||
|
repeated CreateSocketRequest.SocketFamily address_families = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveReply {
|
||||||
|
enum ErrorCode {
|
||||||
|
SOCKET_EAI_ADDRFAMILY = 1;
|
||||||
|
SOCKET_EAI_AGAIN = 2;
|
||||||
|
SOCKET_EAI_BADFLAGS = 3;
|
||||||
|
SOCKET_EAI_FAIL = 4;
|
||||||
|
SOCKET_EAI_FAMILY = 5;
|
||||||
|
SOCKET_EAI_MEMORY = 6;
|
||||||
|
SOCKET_EAI_NODATA = 7;
|
||||||
|
SOCKET_EAI_NONAME = 8;
|
||||||
|
SOCKET_EAI_SERVICE = 9;
|
||||||
|
SOCKET_EAI_SOCKTYPE = 10;
|
||||||
|
SOCKET_EAI_SYSTEM = 11;
|
||||||
|
SOCKET_EAI_BADHINTS = 12;
|
||||||
|
SOCKET_EAI_PROTOCOL = 13;
|
||||||
|
SOCKET_EAI_OVERFLOW = 14;
|
||||||
|
SOCKET_EAI_MAX = 15;
|
||||||
|
};
|
||||||
|
|
||||||
|
repeated bytes packed_address = 2;
|
||||||
|
optional string canonical_name = 3;
|
||||||
|
repeated string aliases = 4;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2012 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Namespace returns a replacement context that operates within the given namespace.
|
||||||
|
func Namespace(c context.Context, namespace string) (context.Context, error) {
|
||||||
|
if !validNamespace.MatchString(namespace) {
|
||||||
|
return nil, fmt.Errorf("appengine: namespace %q does not match /%s/", namespace, validNamespace)
|
||||||
|
}
|
||||||
|
return internal.NamespacedContext(c, namespace), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validNamespace matches valid namespace names.
|
||||||
|
var validNamespace = regexp.MustCompile(`^[0-9A-Za-z._-]{0,100}$`)
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2012 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package socket provides outbound network sockets.
|
||||||
|
//
|
||||||
|
// This package is only required in the classic App Engine environment.
|
||||||
|
// Applications running only in App Engine "flexible environment" should
|
||||||
|
// use the standard library's net package.
|
||||||
|
package socket
|
|
@ -0,0 +1,290 @@
|
||||||
|
// Copyright 2012 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
|
||||||
|
pb "google.golang.org/appengine/internal/socket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network protocol.
|
||||||
|
// The address format is host:port, where host may be a hostname or an IP address.
|
||||||
|
// Known protocols are "tcp" and "udp".
|
||||||
|
// The returned connection satisfies net.Conn, and is valid while ctx is valid;
|
||||||
|
// if the connection is to be used after ctx becomes invalid, invoke SetContext
|
||||||
|
// with the new context.
|
||||||
|
func Dial(ctx context.Context, protocol, addr string) (*Conn, error) {
|
||||||
|
return DialTimeout(ctx, protocol, addr, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipFamilies = []pb.CreateSocketRequest_SocketFamily{
|
||||||
|
pb.CreateSocketRequest_IPv4,
|
||||||
|
pb.CreateSocketRequest_IPv6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout is like Dial but takes a timeout.
|
||||||
|
// The timeout includes name resolution, if required.
|
||||||
|
func DialTimeout(ctx context.Context, protocol, addr string, timeout time.Duration) (*Conn, error) {
|
||||||
|
dialCtx := ctx // Used for dialing and name resolution, but not stored in the *Conn.
|
||||||
|
if timeout > 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dialCtx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("socket: bad port %q: %v", portStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prot pb.CreateSocketRequest_SocketProtocol
|
||||||
|
switch protocol {
|
||||||
|
case "tcp":
|
||||||
|
prot = pb.CreateSocketRequest_TCP
|
||||||
|
case "udp":
|
||||||
|
prot = pb.CreateSocketRequest_UDP
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("socket: unknown protocol %q", protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
packedAddrs, resolved, err := resolve(dialCtx, ipFamilies, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("socket: failed resolving %q: %v", host, err)
|
||||||
|
}
|
||||||
|
if len(packedAddrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no addresses for %q", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
packedAddr := packedAddrs[0] // use first address
|
||||||
|
fam := pb.CreateSocketRequest_IPv4
|
||||||
|
if len(packedAddr) == net.IPv6len {
|
||||||
|
fam = pb.CreateSocketRequest_IPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &pb.CreateSocketRequest{
|
||||||
|
Family: fam.Enum(),
|
||||||
|
Protocol: prot.Enum(),
|
||||||
|
RemoteIp: &pb.AddressPort{
|
||||||
|
Port: proto.Int32(int32(port)),
|
||||||
|
PackedAddress: packedAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if resolved {
|
||||||
|
req.RemoteIp.HostnameHint = &host
|
||||||
|
}
|
||||||
|
res := &pb.CreateSocketReply{}
|
||||||
|
if err := internal.Call(dialCtx, "remote_socket", "CreateSocket", req, res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conn{
|
||||||
|
ctx: ctx,
|
||||||
|
desc: res.GetSocketDescriptor(),
|
||||||
|
prot: prot,
|
||||||
|
local: res.ProxyExternalIp,
|
||||||
|
remote: req.RemoteIp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP returns the given host's IP addresses.
|
||||||
|
func LookupIP(ctx context.Context, host string) (addrs []net.IP, err error) {
|
||||||
|
packedAddrs, _, err := resolve(ctx, ipFamilies, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("socket: failed resolving %q: %v", host, err)
|
||||||
|
}
|
||||||
|
addrs = make([]net.IP, len(packedAddrs))
|
||||||
|
for i, pa := range packedAddrs {
|
||||||
|
addrs[i] = net.IP(pa)
|
||||||
|
}
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(ctx context.Context, fams []pb.CreateSocketRequest_SocketFamily, host string) ([][]byte, bool, error) {
|
||||||
|
// Check if it's an IP address.
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip := ip.To4(); ip != nil {
|
||||||
|
return [][]byte{ip}, false, nil
|
||||||
|
}
|
||||||
|
return [][]byte{ip}, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &pb.ResolveRequest{
|
||||||
|
Name: &host,
|
||||||
|
AddressFamilies: fams,
|
||||||
|
}
|
||||||
|
res := &pb.ResolveReply{}
|
||||||
|
if err := internal.Call(ctx, "remote_socket", "Resolve", req, res); err != nil {
|
||||||
|
// XXX: need to map to pb.ResolveReply_ErrorCode?
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return res.PackedAddress, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// withDeadline is like context.WithDeadline, except it ignores the zero deadline.
|
||||||
|
func withDeadline(parent context.Context, deadline time.Time) (context.Context, context.CancelFunc) {
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return parent, func() {}
|
||||||
|
}
|
||||||
|
return context.WithDeadline(parent, deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents a socket connection.
|
||||||
|
// It implements net.Conn.
|
||||||
|
type Conn struct {
|
||||||
|
ctx context.Context
|
||||||
|
desc string
|
||||||
|
offset int64
|
||||||
|
|
||||||
|
prot pb.CreateSocketRequest_SocketProtocol
|
||||||
|
local, remote *pb.AddressPort
|
||||||
|
|
||||||
|
readDeadline, writeDeadline time.Time // optional
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets the context that is used by this Conn.
|
||||||
|
// It is usually used only when using a Conn that was created in a different context,
|
||||||
|
// such as when a connection is created during a warmup request but used while
|
||||||
|
// servicing a user request.
|
||||||
|
func (cn *Conn) SetContext(ctx context.Context) {
|
||||||
|
cn.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Read(b []byte) (n int, err error) {
|
||||||
|
const maxRead = 1 << 20
|
||||||
|
if len(b) > maxRead {
|
||||||
|
b = b[:maxRead]
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &pb.ReceiveRequest{
|
||||||
|
SocketDescriptor: &cn.desc,
|
||||||
|
DataSize: proto.Int32(int32(len(b))),
|
||||||
|
}
|
||||||
|
res := &pb.ReceiveReply{}
|
||||||
|
if !cn.readDeadline.IsZero() {
|
||||||
|
req.TimeoutSeconds = proto.Float64(cn.readDeadline.Sub(time.Now()).Seconds())
|
||||||
|
}
|
||||||
|
ctx, cancel := withDeadline(cn.ctx, cn.readDeadline)
|
||||||
|
defer cancel()
|
||||||
|
if err := internal.Call(ctx, "remote_socket", "Receive", req, res); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(res.Data) == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if len(res.Data) > len(b) {
|
||||||
|
return 0, fmt.Errorf("socket: internal error: read too much data: %d > %d", len(res.Data), len(b))
|
||||||
|
}
|
||||||
|
return copy(b, res.Data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Write(b []byte) (n int, err error) {
|
||||||
|
const lim = 1 << 20 // max per chunk
|
||||||
|
|
||||||
|
for n < len(b) {
|
||||||
|
chunk := b[n:]
|
||||||
|
if len(chunk) > lim {
|
||||||
|
chunk = chunk[:lim]
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &pb.SendRequest{
|
||||||
|
SocketDescriptor: &cn.desc,
|
||||||
|
Data: chunk,
|
||||||
|
StreamOffset: &cn.offset,
|
||||||
|
}
|
||||||
|
res := &pb.SendReply{}
|
||||||
|
if !cn.writeDeadline.IsZero() {
|
||||||
|
req.TimeoutSeconds = proto.Float64(cn.writeDeadline.Sub(time.Now()).Seconds())
|
||||||
|
}
|
||||||
|
ctx, cancel := withDeadline(cn.ctx, cn.writeDeadline)
|
||||||
|
defer cancel()
|
||||||
|
if err = internal.Call(ctx, "remote_socket", "Send", req, res); err != nil {
|
||||||
|
// assume zero bytes were sent in this RPC
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += int(res.GetDataSent())
|
||||||
|
cn.offset += int64(res.GetDataSent())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Close() error {
|
||||||
|
req := &pb.CloseRequest{
|
||||||
|
SocketDescriptor: &cn.desc,
|
||||||
|
}
|
||||||
|
res := &pb.CloseReply{}
|
||||||
|
if err := internal.Call(cn.ctx, "remote_socket", "Close", req, res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cn.desc = "CLOSED"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addr(prot pb.CreateSocketRequest_SocketProtocol, ap *pb.AddressPort) net.Addr {
|
||||||
|
if ap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch prot {
|
||||||
|
case pb.CreateSocketRequest_TCP:
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: net.IP(ap.PackedAddress),
|
||||||
|
Port: int(*ap.Port),
|
||||||
|
}
|
||||||
|
case pb.CreateSocketRequest_UDP:
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: net.IP(ap.PackedAddress),
|
||||||
|
Port: int(*ap.Port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unknown protocol " + prot.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) LocalAddr() net.Addr { return addr(cn.prot, cn.local) }
|
||||||
|
func (cn *Conn) RemoteAddr() net.Addr { return addr(cn.prot, cn.remote) }
|
||||||
|
|
||||||
|
func (cn *Conn) SetDeadline(t time.Time) error {
|
||||||
|
cn.readDeadline = t
|
||||||
|
cn.writeDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
cn.readDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
cn.writeDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeepAlive signals that the connection is still in use.
|
||||||
|
// It may be called to prevent the socket being closed due to inactivity.
|
||||||
|
func (cn *Conn) KeepAlive() error {
|
||||||
|
req := &pb.GetSocketNameRequest{
|
||||||
|
SocketDescriptor: &cn.desc,
|
||||||
|
}
|
||||||
|
res := &pb.GetSocketNameReply{}
|
||||||
|
return internal.Call(cn.ctx, "remote_socket", "GetSocketName", req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.RegisterErrorCodeMap("remote_socket", pb.RemoteSocketServiceError_ErrorCode_name)
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network protocol.
|
||||||
|
// The address format is host:port, where host may be a hostname or an IP address.
|
||||||
|
// Known protocols are "tcp" and "udp".
|
||||||
|
// The returned connection satisfies net.Conn, and is valid while ctx is valid;
|
||||||
|
// if the connection is to be used after ctx becomes invalid, invoke SetContext
|
||||||
|
// with the new context.
|
||||||
|
func Dial(ctx context.Context, protocol, addr string) (*Conn, error) {
|
||||||
|
conn, err := net.Dial(protocol, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Conn{conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout is like Dial but takes a timeout.
|
||||||
|
// The timeout includes name resolution, if required.
|
||||||
|
func DialTimeout(ctx context.Context, protocol, addr string, timeout time.Duration) (*Conn, error) {
|
||||||
|
conn, err := net.DialTimeout(protocol, addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Conn{conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP returns the given host's IP addresses.
|
||||||
|
func LookupIP(ctx context.Context, host string) (addrs []net.IP, err error) {
|
||||||
|
return net.LookupIP(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents a socket connection.
|
||||||
|
// It implements net.Conn.
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets the context that is used by this Conn.
|
||||||
|
// It is usually used only when using a Conn that was created in a different context,
|
||||||
|
// such as when a connection is created during a warmup request but used while
|
||||||
|
// servicing a user request.
|
||||||
|
func (cn *Conn) SetContext(ctx context.Context) {
|
||||||
|
// This function is not required in App Engine "flexible environment".
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeepAlive signals that the connection is still in use.
|
||||||
|
// It may be called to prevent the socket being closed due to inactivity.
|
||||||
|
func (cn *Conn) KeepAlive() error {
|
||||||
|
// This function is not required in App Engine "flexible environment".
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2013 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import "golang.org/x/net/context"
|
||||||
|
|
||||||
|
// IsTimeoutError reports whether err is a timeout error.
|
||||||
|
func IsTimeoutError(err error) bool {
|
||||||
|
if err == context.DeadlineExceeded {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t, ok := err.(interface {
|
||||||
|
IsTimeout() bool
|
||||||
|
}); ok {
|
||||||
|
return t.IsTimeout()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2015, Google Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following disclaimer
|
||||||
|
* in the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
* * Neither the name of Google Inc. nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package oauth implements gRPC credentials using OAuth.
|
||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenSource supplies PerRPCCredentials from an oauth2.TokenSource.
|
||||||
|
type TokenSource struct {
|
||||||
|
oauth2.TokenSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestMetadata gets the request metadata as a map from a TokenSource.
|
||||||
|
func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
token, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": token.Type() + " " + token.AccessToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireTransportSecurity indicates whether the credentials requires transport security.
|
||||||
|
func (ts TokenSource) RequireTransportSecurity() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtAccess struct {
|
||||||
|
jsonKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWTAccessFromFile creates PerRPCCredentials from the given keyFile.
|
||||||
|
func NewJWTAccessFromFile(keyFile string) (credentials.PerRPCCredentials, error) {
|
||||||
|
jsonKey, err := ioutil.ReadFile(keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err)
|
||||||
|
}
|
||||||
|
return NewJWTAccessFromKey(jsonKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWTAccessFromKey creates PerRPCCredentials from the given jsonKey.
|
||||||
|
func NewJWTAccessFromKey(jsonKey []byte) (credentials.PerRPCCredentials, error) {
|
||||||
|
return jwtAccess{jsonKey}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jwtAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
ts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, uri[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": token.TokenType + " " + token.AccessToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jwtAccess) RequireTransportSecurity() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// oauthAccess supplies PerRPCCredentials from a given token.
|
||||||
|
type oauthAccess struct {
|
||||||
|
token oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOauthAccess constructs the PerRPCCredentials using a given token.
|
||||||
|
func NewOauthAccess(token *oauth2.Token) credentials.PerRPCCredentials {
|
||||||
|
return oauthAccess{token: *token}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oa oauthAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": oa.token.TokenType + " " + oa.token.AccessToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oa oauthAccess) RequireTransportSecurity() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComputeEngine constructs the PerRPCCredentials that fetches access tokens from
|
||||||
|
// Google Compute Engine (GCE)'s metadata server. It is only valid to use this
|
||||||
|
// if your program is running on a GCE instance.
|
||||||
|
// TODO(dsymonds): Deprecate and remove this.
|
||||||
|
func NewComputeEngine() credentials.PerRPCCredentials {
|
||||||
|
return TokenSource{google.ComputeTokenSource("")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccount represents PerRPCCredentials via JWT signing key.
|
||||||
|
type serviceAccount struct {
|
||||||
|
config *jwt.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceAccount) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||||
|
token, err := s.config.TokenSource(ctx).Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": token.TokenType + " " + token.AccessToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceAccount) RequireTransportSecurity() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice
|
||||||
|
// from a Google Developers service account.
|
||||||
|
func NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) {
|
||||||
|
config, err := google.JWTConfigFromJSON(jsonKey, scope...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return serviceAccount{config: config}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceAccountFromFile constructs the PerRPCCredentials using the JSON key file
|
||||||
|
// of a Google Developers service account.
|
||||||
|
func NewServiceAccountFromFile(keyFile string, scope ...string) (credentials.PerRPCCredentials, error) {
|
||||||
|
jsonKey, err := ioutil.ReadFile(keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err)
|
||||||
|
}
|
||||||
|
return NewServiceAccountFromKey(jsonKey, scope...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplicationDefault returns "Application Default Credentials". For more
|
||||||
|
// detail, see https://developers.google.com/accounts/docs/application-default-credentials.
|
||||||
|
func NewApplicationDefault(ctx context.Context, scope ...string) (credentials.PerRPCCredentials, error) {
|
||||||
|
t, err := google.DefaultTokenSource(ctx, scope...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return TokenSource{t}, nil
|
||||||
|
}
|
|
@ -19,7 +19,31 @@
|
||||||
"revision": ""
|
"revision": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "Xt8qcx6rrE1w8HkRSoxJDlNxswA=",
|
"checksumSHA1": "ZLRh6zW4/DnVsGpgtt+ZiIaEFKc=",
|
||||||
|
"path": "cloud.google.com/go/compute/metadata",
|
||||||
|
"revision": "2c3878eb121ed42f23fd78e7209e3755006117cc",
|
||||||
|
"revisionTime": "2016-11-16T20:27:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "hiJXjkFEGy+sDFf6O58Ocdy9Rnk=",
|
||||||
|
"path": "cloud.google.com/go/internal",
|
||||||
|
"revision": "2c3878eb121ed42f23fd78e7209e3755006117cc",
|
||||||
|
"revisionTime": "2016-11-16T20:27:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "W2xJ0+fvugRhRi1PMi64bYofBbU=",
|
||||||
|
"path": "cloud.google.com/go/internal/optional",
|
||||||
|
"revision": "2c3878eb121ed42f23fd78e7209e3755006117cc",
|
||||||
|
"revisionTime": "2016-11-16T20:27:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "QKzg4ee34k/r7wQdiRE2oMNwC+Q=",
|
||||||
|
"path": "cloud.google.com/go/storage",
|
||||||
|
"revision": "2c3878eb121ed42f23fd78e7209e3755006117cc",
|
||||||
|
"revisionTime": "2016-11-16T20:27:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "M30X+Wqn7AnUr1numUOkQRI7ET0=",
|
||||||
"path": "github.com/Azure/azure-sdk-for-go/storage",
|
"path": "github.com/Azure/azure-sdk-for-go/storage",
|
||||||
"revision": "27ae5c8b5bc5d90ab0540b4c5d0f2632c8db8b57",
|
"revision": "27ae5c8b5bc5d90ab0540b4c5d0f2632c8db8b57",
|
||||||
"revisionTime": "2016-11-10T23:35:32Z"
|
"revisionTime": "2016-11-10T23:35:32Z"
|
||||||
|
@ -444,6 +468,12 @@
|
||||||
"revision": "9235644dd9e52eeae6fa48efd539fdc351a0af53",
|
"revision": "9235644dd9e52eeae6fa48efd539fdc351a0af53",
|
||||||
"revisionTime": "2016-03-11T01:20:12Z"
|
"revisionTime": "2016-03-11T01:20:12Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "V/53BpqgOkSDZCX6snQCAkdO2fM=",
|
||||||
|
"path": "github.com/googleapis/gax-go",
|
||||||
|
"revision": "da06d194a00e19ce00d9011a13931c3f6f6887c7",
|
||||||
|
"revisionTime": "2016-11-07T00:24:06Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "O0r0hj4YL+jSRNjnshkeH4GY+4s=",
|
"checksumSHA1": "O0r0hj4YL+jSRNjnshkeH4GY+4s=",
|
||||||
"path": "github.com/hailocab/go-hostpool",
|
"path": "github.com/hailocab/go-hostpool",
|
||||||
|
@ -859,7 +889,13 @@
|
||||||
"revisionTime": "2016-11-15T21:05:04Z"
|
"revisionTime": "2016-11-15T21:05:04Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "pLsZUQhI8jm3W9R/4JO9D/L1cUA=",
|
"checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=",
|
||||||
|
"path": "golang.org/x/net/context/ctxhttp",
|
||||||
|
"revision": "4971afdc2f162e82d185353533d3cf16188a9f4e",
|
||||||
|
"revisionTime": "2016-11-15T21:05:04Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "r+7Ol7uTCa/i5W8pNej9M8xZxWg=",
|
||||||
"path": "golang.org/x/net/http2",
|
"path": "golang.org/x/net/http2",
|
||||||
"revision": "4971afdc2f162e82d185353533d3cf16188a9f4e",
|
"revision": "4971afdc2f162e82d185353533d3cf16188a9f4e",
|
||||||
"revisionTime": "2016-11-15T21:05:04Z"
|
"revisionTime": "2016-11-15T21:05:04Z"
|
||||||
|
@ -900,6 +936,12 @@
|
||||||
"revision": "d5040cddfc0da40b408c9a1da4728662435176a9",
|
"revision": "d5040cddfc0da40b408c9a1da4728662435176a9",
|
||||||
"revisionTime": "2016-11-03T22:50:36Z"
|
"revisionTime": "2016-11-03T22:50:36Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "PoyV7Nub4q+mUX6Jo/TSYcqn8sk=",
|
||||||
|
"path": "golang.org/x/oauth2/google",
|
||||||
|
"revision": "d5040cddfc0da40b408c9a1da4728662435176a9",
|
||||||
|
"revisionTime": "2016-11-03T22:50:36Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=",
|
"checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=",
|
||||||
"path": "golang.org/x/oauth2/internal",
|
"path": "golang.org/x/oauth2/internal",
|
||||||
|
@ -907,17 +949,95 @@
|
||||||
"revisionTime": "2016-11-03T22:50:36Z"
|
"revisionTime": "2016-11-03T22:50:36Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "KqecwXo3OO+p4N+E9RhlHvl9I+w=",
|
"checksumSHA1": "huVltYnXdRFDJLgp/ZP9IALzG7g=",
|
||||||
|
"path": "golang.org/x/oauth2/jws",
|
||||||
|
"revision": "d5040cddfc0da40b408c9a1da4728662435176a9",
|
||||||
|
"revisionTime": "2016-11-03T22:50:36Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "McqNj0/805YfYQJQGomeB0s+EcU=",
|
||||||
|
"path": "golang.org/x/oauth2/jwt",
|
||||||
|
"revision": "d5040cddfc0da40b408c9a1da4728662435176a9",
|
||||||
|
"revisionTime": "2016-11-03T22:50:36Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "aVgPDgwY3/t4J/JOw9H3FVMHqh0=",
|
||||||
"path": "golang.org/x/sys/unix",
|
"path": "golang.org/x/sys/unix",
|
||||||
"revision": "b699b7032584f0953262cb2788a0ca19bb494703",
|
"revision": "b699b7032584f0953262cb2788a0ca19bb494703",
|
||||||
"revisionTime": "2016-11-10T11:58:56Z"
|
"revisionTime": "2016-11-10T11:58:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "gYHoPrPncGO926bN0jr1rzDxBQU=",
|
"checksumSHA1": "I1JSeU5OMapl+4s2VrnBkMon3Bw=",
|
||||||
|
"path": "google.golang.org/api/gensupport",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "okBeNPclS/nvIkPEH/tZa3cxDnM=",
|
||||||
|
"path": "google.golang.org/api/googleapi",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "1K0JxrUfDqAB3MyRiU1LKjfHyf4=",
|
||||||
|
"path": "google.golang.org/api/googleapi/internal/uritemplates",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "Mr2fXhMRzlQCgANFm91s536pG7E=",
|
||||||
|
"path": "google.golang.org/api/googleapi/transport",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "nefIfmUzE2DJD4Tpodz+WHQLfeE=",
|
||||||
|
"path": "google.golang.org/api/internal",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "slcGOTGSdukEPPSN81Q5WZGmhog=",
|
||||||
|
"path": "google.golang.org/api/iterator",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "kEQSHsbkLxyIijEvp5W2SF3uqsU=",
|
||||||
|
"path": "google.golang.org/api/option",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "faJmca8+laTqmOwHzu8Ll5/Nnag=",
|
||||||
|
"path": "google.golang.org/api/storage/v1",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "zRukwFw3vpQ1QXjf0ryq7fLoSCY=",
|
||||||
|
"path": "google.golang.org/api/transport",
|
||||||
|
"revision": "e4c04685e5d7db47ff294aa9e514b3a638c431c9",
|
||||||
|
"revisionTime": "2016-11-16T17:53:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "BYNXzv50n5HWU4OpKBw9JlrKIRI=",
|
||||||
|
"path": "google.golang.org/appengine",
|
||||||
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=",
|
||||||
"path": "google.golang.org/appengine/internal",
|
"path": "google.golang.org/appengine/internal",
|
||||||
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
"revisionTime": "2016-11-15T22:01:06Z"
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "x6Thdfyasqd68dWZWqzWWeIfAfI=",
|
||||||
|
"path": "google.golang.org/appengine/internal/app_identity",
|
||||||
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=",
|
"checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=",
|
||||||
"path": "google.golang.org/appengine/internal/base",
|
"path": "google.golang.org/appengine/internal/base",
|
||||||
|
@ -936,18 +1056,36 @@
|
||||||
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
"revisionTime": "2016-11-15T22:01:06Z"
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "eLZVX1EHLclFtQnjDIszsdyWRHo=",
|
||||||
|
"path": "google.golang.org/appengine/internal/modules",
|
||||||
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=",
|
"checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=",
|
||||||
"path": "google.golang.org/appengine/internal/remote_api",
|
"path": "google.golang.org/appengine/internal/remote_api",
|
||||||
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
"revisionTime": "2016-11-15T22:01:06Z"
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "VA88sOHmVuIslrbHaWx9yEvjGjM=",
|
||||||
|
"path": "google.golang.org/appengine/internal/socket",
|
||||||
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=",
|
"checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=",
|
||||||
"path": "google.golang.org/appengine/internal/urlfetch",
|
"path": "google.golang.org/appengine/internal/urlfetch",
|
||||||
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
"revisionTime": "2016-11-15T22:01:06Z"
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "MharNMGnQusRPdmBYXDxz2cCHPU=",
|
||||||
|
"path": "google.golang.org/appengine/socket",
|
||||||
|
"revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8",
|
||||||
|
"revisionTime": "2016-11-15T22:01:06Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=",
|
"checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=",
|
||||||
"path": "google.golang.org/appengine/urlfetch",
|
"path": "google.golang.org/appengine/urlfetch",
|
||||||
|
@ -972,6 +1110,12 @@
|
||||||
"revision": "941cc894cea3c87a12943fd12b594964541b6d28",
|
"revision": "941cc894cea3c87a12943fd12b594964541b6d28",
|
||||||
"revisionTime": "2016-11-15T20:54:09Z"
|
"revisionTime": "2016-11-15T20:54:09Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "bg3wIPzajKt3QZfTG70EPaxDtpk=",
|
||||||
|
"path": "google.golang.org/grpc/credentials/oauth",
|
||||||
|
"revision": "b13ef794997191ec801db90a42c50034df91850a",
|
||||||
|
"revisionTime": "2016-11-17T01:42:18Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=",
|
"checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=",
|
||||||
"path": "google.golang.org/grpc/grpclog",
|
"path": "google.golang.org/grpc/grpclog",
|
||||||
|
|
|
@ -247,6 +247,9 @@ to help you, but may refer you to the backend author.
|
||||||
* `s3` - Store data within an S3 bucket [S3](https://aws.amazon.com/s3/).
|
* `s3` - Store data within an S3 bucket [S3](https://aws.amazon.com/s3/).
|
||||||
This backend does not support HA. This is a community-supported backend.
|
This backend does not support HA. This is a community-supported backend.
|
||||||
|
|
||||||
|
* `gcs` - Store data within a [Google Cloud Storage](https://cloud.google.com/storage/) bucket.
|
||||||
|
This backend does not support HA. This is a community-supported backend.
|
||||||
|
|
||||||
* `azure` - Store data in an Azure Storage container [Azure](https://azure.microsoft.com/en-us/services/storage/).
|
* `azure` - Store data in an Azure Storage container [Azure](https://azure.microsoft.com/en-us/services/storage/).
|
||||||
This backend does not support HA. This is a community-supported backend.
|
This backend does not support HA. This is a community-supported backend.
|
||||||
|
|
||||||
|
@ -611,6 +614,17 @@ will cause Vault to attempt to retrieve credentials from the metadata service.
|
||||||
You are responsible for ensuring your instance is launched with the appropriate
|
You are responsible for ensuring your instance is launched with the appropriate
|
||||||
profile enabled. Vault will handle renewing profile credentials as they rotate.
|
profile enabled. Vault will handle renewing profile credentials as they rotate.
|
||||||
|
|
||||||
|
#### Backend Reference: Google Cloud Storage (Community-Supported)
|
||||||
|
|
||||||
|
For Google Cloud Storage, the following options are supported:
|
||||||
|
|
||||||
|
* `bucket` (required) - The name of the Google Cloud Storage bucket to use. It must be provided, but it can also be sourced from the `GOOGLE_STORAGE_BUCKET` environment variable.
|
||||||
|
|
||||||
|
* `credentials_file` - (required) The path to a GCP [service account](https://cloud.google.com/compute/docs/access/service-accounts) private key file in [JSON format](https://cloud.google.com/storage/docs/authentication#generating-a-private-key). It must be provided, but it can also be sourced from the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
|
||||||
|
|
||||||
|
* `max_parallel` (optional) - The maximum number of concurrent requests to Google Cloud Storage.
|
||||||
|
Defaults to `"128"`.
|
||||||
|
|
||||||
#### Backend Reference: Azure (Community-Supported)
|
#### Backend Reference: Azure (Community-Supported)
|
||||||
|
|
||||||
* `accountName` (required) - The Azure Storage account name
|
* `accountName` (required) - The Azure Storage account name
|
||||||
|
|
Loading…
Reference in New Issue