mirror of https://github.com/docker/docs.git
GCE Driver.
Signed-off-by: Dan Lorenc <lorenc.d@gmail.com>
This commit is contained in:
parent
f43a0f2a9a
commit
17d8b16a37
|
@ -1,12 +1,22 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/machine",
|
"ImportPath": "github.com/docker/machine",
|
||||||
"GoVersion": "go1.3",
|
"GoVersion": "go1.3.3",
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "code.google.com/p/goauth2/oauth",
|
"ImportPath": "code.google.com/p/goauth2/oauth",
|
||||||
"Comment": "weekly-56",
|
"Comment": "weekly-56",
|
||||||
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
|
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/google-api-go-client/compute/v1",
|
||||||
|
"Comment": "release-107",
|
||||||
|
"Rev": "6ddfebb10ece847f1ae09c701834f1b15abbd8b2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/google-api-go-client/googleapi",
|
||||||
|
"Comment": "release-107",
|
||||||
|
"Rev": "6ddfebb10ece847f1ae09c701834f1b15abbd8b2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
|
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
|
||||||
"Comment": "v1.1-14-g814812a",
|
"Comment": "v1.1-14-g814812a",
|
||||||
|
|
9526
Godeps/_workspace/src/code.google.com/p/google-api-go-client/compute/v1/compute-api.json
generated
vendored
Normal file
9526
Godeps/_workspace/src/code.google.com/p/google-api-go-client/compute/v1/compute-api.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16952
Godeps/_workspace/src/code.google.com/p/google-api-go-client/compute/v1/compute-gen.go
generated
vendored
Normal file
16952
Godeps/_workspace/src/code.google.com/p/google-api-go-client/compute/v1/compute-gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
401
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/googleapi.go
generated
vendored
Normal file
401
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/googleapi.go
generated
vendored
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/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
|
||||||
|
}
|
||||||
|
|
||||||
|
const Version = "0.5"
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMediaType(media io.Reader) (io.Reader, string) {
|
||||||
|
if typer, ok := media.(ContentTyper); ok {
|
||||||
|
return media, typer.ContentType()
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := "application/octet-stream"
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
n, err := media.Read(buf)
|
||||||
|
buf = buf[:n]
|
||||||
|
if err == nil {
|
||||||
|
typ = http.DetectContentType(buf)
|
||||||
|
}
|
||||||
|
return io.MultiReader(bytes.NewBuffer(buf), media), typ
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lengther interface {
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// endingWithErrorReader from r until it returns an error. If the
|
||||||
|
// final error from r is os.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReaderSize(r io.Reader) (io.Reader, int64) {
|
||||||
|
// Ideal case, the reader knows its own size.
|
||||||
|
if lr, ok := r.(Lengther); ok {
|
||||||
|
return r, int64(lr.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// But maybe it's a seeker and we can seek to the end to find its size.
|
||||||
|
if s, ok := r.(io.Seeker); ok {
|
||||||
|
pos0, err := s.Seek(0, os.SEEK_CUR)
|
||||||
|
if err == nil {
|
||||||
|
posend, err := s.Seek(0, os.SEEK_END)
|
||||||
|
if err == nil {
|
||||||
|
_, err = s.Seek(pos0, os.SEEK_SET)
|
||||||
|
if err == nil {
|
||||||
|
return r, posend - pos0
|
||||||
|
} else {
|
||||||
|
// We moved it forward but can't restore it.
|
||||||
|
// Seems unlikely, but can't really restore now.
|
||||||
|
return endingWithErrorReader{strings.NewReader(""), err}, posend - pos0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we have to make a copy to calculate how big the reader is.
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
// TODO(bradfitz): put a cap on this copy? spill to disk after
|
||||||
|
// a certain point?
|
||||||
|
_, err := io.Copy(buf, r)
|
||||||
|
return endingWithErrorReader{buf, err}, int64(buf.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeHeader(contentType string) textproto.MIMEHeader {
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
h.Set("Content-Type", contentType)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConditionallyIncludeMedia does nothing if media is nil.
|
||||||
|
//
|
||||||
|
// bodyp is an in/out parameter. It should initially point to the
|
||||||
|
// reader of the application/json (or whatever) payload to send in the
|
||||||
|
// API request. It's updated to point to the multipart body reader.
|
||||||
|
//
|
||||||
|
// ctypep is an in/out parameter. It should initially point to the
|
||||||
|
// content type of the bodyp, usually "application/json". It's updated
|
||||||
|
// to the "multipart/related" content type, with random boundary.
|
||||||
|
//
|
||||||
|
// The return value is the content-length of the entire multpart body.
|
||||||
|
func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (totalContentLength int64, ok bool) {
|
||||||
|
if media == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get the media type and size. The type check might return a
|
||||||
|
// different reader instance, so do the size check first,
|
||||||
|
// which looks at the specific type of the io.Reader.
|
||||||
|
var mediaType string
|
||||||
|
if typer, ok := media.(ContentTyper); ok {
|
||||||
|
mediaType = typer.ContentType()
|
||||||
|
}
|
||||||
|
media, mediaSize := getReaderSize(media)
|
||||||
|
if mediaType == "" {
|
||||||
|
media, mediaType = getMediaType(media)
|
||||||
|
}
|
||||||
|
body, bodyType := *bodyp, *ctypep
|
||||||
|
body, bodySize := getReaderSize(body)
|
||||||
|
|
||||||
|
// Calculate how big the the multipart will be.
|
||||||
|
{
|
||||||
|
totalContentLength = bodySize + mediaSize
|
||||||
|
mpw := multipart.NewWriter(countingWriter{&totalContentLength})
|
||||||
|
mpw.CreatePart(typeHeader(bodyType))
|
||||||
|
mpw.CreatePart(typeHeader(mediaType))
|
||||||
|
mpw.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
mpw := multipart.NewWriter(pw)
|
||||||
|
*bodyp = pr
|
||||||
|
*ctypep = "multipart/related; boundary=" + mpw.Boundary()
|
||||||
|
go func() {
|
||||||
|
defer pw.Close()
|
||||||
|
defer mpw.Close()
|
||||||
|
|
||||||
|
w, err := mpw.CreatePart(typeHeader(bodyType))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err = mpw.CreatePart(typeHeader(mediaType))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, media)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return totalContentLength, true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// has4860Fix is whether this Go environment contains the fix for
|
||||||
|
// http://golang.org/issue/4860
|
||||||
|
var has4860Fix bool
|
||||||
|
|
||||||
|
// init initializes has4860Fix by checking the behavior of the net/http package.
|
||||||
|
func init() {
|
||||||
|
r := http.Request{
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Opaque: "//opaque",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
r.Write(b)
|
||||||
|
has4860Fix = bytes.HasPrefix(b.Bytes(), []byte("GET http"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpaque sets u.Opaque from u.Path such that HTTP requests to it
|
||||||
|
// don't alter any hex-escaped characters in u.Path.
|
||||||
|
func SetOpaque(u *url.URL) {
|
||||||
|
u.Opaque = "//" + u.Host + u.Path
|
||||||
|
if !has4860Fix {
|
||||||
|
u.Opaque = u.Scheme + ":" + u.Opaque
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
expanded, err := uritemplates.Expand(u.Path, expansions)
|
||||||
|
if err == nil {
|
||||||
|
u.Path = expanded
|
||||||
|
SetOpaque(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, ",")
|
||||||
|
}
|
361
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go
generated
vendored
Normal file
361
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go
generated
vendored
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetOpaqueTest struct {
|
||||||
|
in *url.URL
|
||||||
|
wantRequestURI string
|
||||||
|
}
|
||||||
|
|
||||||
|
var setOpaqueTests = []SetOpaqueTest{
|
||||||
|
// no path
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
},
|
||||||
|
"http://www.golang.org",
|
||||||
|
},
|
||||||
|
// path
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
Path: "/",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/",
|
||||||
|
},
|
||||||
|
// file with hex escaping
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
Path: "/file%20one&two",
|
||||||
|
},
|
||||||
|
"https://www.golang.org/file%20one&two",
|
||||||
|
},
|
||||||
|
// query
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
Path: "/",
|
||||||
|
RawQuery: "q=go+language",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/?q=go+language",
|
||||||
|
},
|
||||||
|
// file with hex escaping in path plus query
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
Path: "/file%20one&two",
|
||||||
|
RawQuery: "q=go+language",
|
||||||
|
},
|
||||||
|
"https://www.golang.org/file%20one&two?q=go+language",
|
||||||
|
},
|
||||||
|
// query with hex escaping
|
||||||
|
{
|
||||||
|
&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "www.golang.org",
|
||||||
|
Path: "/",
|
||||||
|
RawQuery: "q=go%20language",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/?q=go%20language",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixTmpl is a template for the expected prefix of the output of writing
|
||||||
|
// an HTTP request.
|
||||||
|
const prefixTmpl = "GET %v HTTP/1.1\r\nHost: %v\r\n"
|
||||||
|
|
||||||
|
func TestSetOpaque(t *testing.T) {
|
||||||
|
for _, test := range setOpaqueTests {
|
||||||
|
u := *test.in
|
||||||
|
SetOpaque(&u)
|
||||||
|
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
r := &http.Request{URL: &u}
|
||||||
|
if err := r.Write(w); err != nil {
|
||||||
|
t.Errorf("write request: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf(prefixTmpl, test.wantRequestURI, test.in.Host)
|
||||||
|
if got := string(w.Bytes()); !strings.HasPrefix(got, prefix) {
|
||||||
|
t.Errorf("got %q expected prefix %q", got, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpandTest struct {
|
||||||
|
in string
|
||||||
|
expansions map[string]string
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandTests = []ExpandTest{
|
||||||
|
// no expansions
|
||||||
|
{
|
||||||
|
"http://www.golang.org/",
|
||||||
|
map[string]string{},
|
||||||
|
"http://www.golang.org/",
|
||||||
|
},
|
||||||
|
// one expansion, no escaping
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket}/delete",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "red",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/red/delete",
|
||||||
|
},
|
||||||
|
// one expansion, with hex escapes
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket}/delete",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "red/blue",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/red%2Fblue/delete",
|
||||||
|
},
|
||||||
|
// one expansion, with space
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket}/delete",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "red or blue",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/red%20or%20blue/delete",
|
||||||
|
},
|
||||||
|
// expansion not found
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{object}/delete",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "red or blue",
|
||||||
|
},
|
||||||
|
"http://www.golang.org//delete",
|
||||||
|
},
|
||||||
|
// multiple expansions
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{one}/{two}/{three}/get",
|
||||||
|
map[string]string{
|
||||||
|
"one": "ONE",
|
||||||
|
"two": "TWO",
|
||||||
|
"three": "THREE",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/ONE/TWO/THREE/get",
|
||||||
|
},
|
||||||
|
// utf-8 characters
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket}/get",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "£100",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/%C2%A3100/get",
|
||||||
|
},
|
||||||
|
// punctuations
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket}/get",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": `/\@:,.`,
|
||||||
|
},
|
||||||
|
"http://www.golang.org/%2F%5C%40%3A%2C./get",
|
||||||
|
},
|
||||||
|
// mis-matched brackets
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{bucket/get",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "red",
|
||||||
|
},
|
||||||
|
"http://www.golang.org/{bucket/get",
|
||||||
|
},
|
||||||
|
// "+" prefix for suppressing escape
|
||||||
|
// See also: http://tools.ietf.org/html/rfc6570#section-3.2.3
|
||||||
|
{
|
||||||
|
"http://www.golang.org/{+topic}",
|
||||||
|
map[string]string{
|
||||||
|
"topic": "/topics/myproject/mytopic",
|
||||||
|
},
|
||||||
|
// The double slashes here look weird, but it's intentional
|
||||||
|
"http://www.golang.org//topics/myproject/mytopic",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpand(t *testing.T) {
|
||||||
|
for i, test := range expandTests {
|
||||||
|
u := url.URL{
|
||||||
|
Path: test.in,
|
||||||
|
}
|
||||||
|
Expand(&u, test.expansions)
|
||||||
|
got := u.Path
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("got %q expected %q in test %d", got, test.want, i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckResponseTest struct {
|
||||||
|
in *http.Response
|
||||||
|
bodyText string
|
||||||
|
want error
|
||||||
|
errText string
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkResponseTests = []CheckResponseTest{
|
||||||
|
{
|
||||||
|
&http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
`{"error":{}}`,
|
||||||
|
&Error{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Body: `{"error":{}}`,
|
||||||
|
},
|
||||||
|
`googleapi: got HTTP response code 500 with body: {"error":{}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
`{"error":{"message":"Error message for StatusNotFound."}}`,
|
||||||
|
&Error{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Message: "Error message for StatusNotFound.",
|
||||||
|
Body: `{"error":{"message":"Error message for StatusNotFound."}}`,
|
||||||
|
},
|
||||||
|
"googleapi: Error 404: Error message for StatusNotFound.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&http.Response{
|
||||||
|
StatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
`{"error":"invalid_token","error_description":"Invalid Value"}`,
|
||||||
|
&Error{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Body: `{"error":"invalid_token","error_description":"Invalid Value"}`,
|
||||||
|
},
|
||||||
|
`googleapi: got HTTP response code 400 with body: {"error":"invalid_token","error_description":"Invalid Value"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&http.Response{
|
||||||
|
StatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
`{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
|
||||||
|
&Error{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Errors: []ErrorItem{
|
||||||
|
{
|
||||||
|
Reason: "keyInvalid",
|
||||||
|
Message: "Bad Request",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: `{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
|
||||||
|
Message: "Bad Request",
|
||||||
|
},
|
||||||
|
"googleapi: Error 400: Bad Request, keyInvalid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckResponse(t *testing.T) {
|
||||||
|
for _, test := range checkResponseTests {
|
||||||
|
res := test.in
|
||||||
|
if test.bodyText != "" {
|
||||||
|
res.Body = ioutil.NopCloser(strings.NewReader(test.bodyText))
|
||||||
|
}
|
||||||
|
g := CheckResponse(res)
|
||||||
|
if !reflect.DeepEqual(g, test.want) {
|
||||||
|
t.Errorf("CheckResponse: got %v, want %v", g, test.want)
|
||||||
|
gotJson, err := json.Marshal(g)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
wantJson, err := json.Marshal(test.want)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Errorf("json(got): %q\njson(want): %q", string(gotJson), string(wantJson))
|
||||||
|
}
|
||||||
|
if g != nil && g.Error() != test.errText {
|
||||||
|
t.Errorf("CheckResponse: unexpected error message.\nGot: %q\nwant: %q", g, test.errText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type VariantPoint struct {
|
||||||
|
Type string
|
||||||
|
Coordinates []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type VariantTest struct {
|
||||||
|
in map[string]interface{}
|
||||||
|
result bool
|
||||||
|
want VariantPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var coords = []interface{}{1.0, 2.0}
|
||||||
|
|
||||||
|
var variantTests = []VariantTest{
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": coords,
|
||||||
|
},
|
||||||
|
result: true,
|
||||||
|
want: VariantPoint{
|
||||||
|
Type: "Point",
|
||||||
|
Coordinates: []float64{1.0, 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"type": "Point",
|
||||||
|
"bogus": coords,
|
||||||
|
},
|
||||||
|
result: true,
|
||||||
|
want: VariantPoint{
|
||||||
|
Type: "Point",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariantType(t *testing.T) {
|
||||||
|
for _, test := range variantTests {
|
||||||
|
if g := VariantType(test.in); g != test.want.Type {
|
||||||
|
t.Errorf("VariantType(%v): got %v, want %v", test.in, g, test.want.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertVariant(t *testing.T) {
|
||||||
|
for _, test := range variantTests {
|
||||||
|
g := VariantPoint{}
|
||||||
|
r := ConvertVariant(test.in, &g)
|
||||||
|
if r != test.result {
|
||||||
|
t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, r, test.result)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(g, test.want) {
|
||||||
|
t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, g, test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/LICENSE
generated
vendored
Normal file
18
Godeps/_workspace/src/code.google.com/p/google-api-go-client/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.
|
359
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/uritemplates.go
generated
vendored
Normal file
359
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/uritemplates.go
generated
vendored
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
// 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 4 implementation of RFC 6570 (URI
|
||||||
|
// Template, http://tools.ietf.org/html/rfc6570).
|
||||||
|
//
|
||||||
|
// To use uritemplates, parse a template string and expand it with a value
|
||||||
|
// map:
|
||||||
|
//
|
||||||
|
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
|
||||||
|
// values := make(map[string]interface{})
|
||||||
|
// values["user"] = "jtacoma"
|
||||||
|
// values["repo"] = "uritemplates"
|
||||||
|
// expanded, _ := template.ExpandString(values)
|
||||||
|
// fmt.Printf(expanded)
|
||||||
|
//
|
||||||
|
package uritemplates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(s string, allowReserved bool) (escaped string) {
|
||||||
|
if allowReserved {
|
||||||
|
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
|
||||||
|
} else {
|
||||||
|
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
|
||||||
|
}
|
||||||
|
return escaped
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) (template *UriTemplate, err error) {
|
||||||
|
template = new(UriTemplate)
|
||||||
|
template.raw = rawtemplate
|
||||||
|
split := strings.Split(rawtemplate, "{")
|
||||||
|
template.parts = make([]templatePart, len(split)*2-1)
|
||||||
|
for i, s := range split {
|
||||||
|
if i == 0 {
|
||||||
|
if strings.Contains(s, "}") {
|
||||||
|
err = errors.New("unexpected }")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
template.parts[i].raw = s
|
||||||
|
} else {
|
||||||
|
subsplit := strings.Split(s, "}")
|
||||||
|
if len(subsplit) != 2 {
|
||||||
|
err = errors.New("malformed template")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
expression := subsplit[0]
|
||||||
|
template.parts[i*2-1], err = parseExpression(expression)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
template.parts[i*2].raw = subsplit[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
template = nil
|
||||||
|
}
|
||||||
|
return template, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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 a string.
|
||||||
|
func (self *UriTemplate) Expand(value interface{}) (string, error) {
|
||||||
|
values, ismap := value.(map[string]interface{})
|
||||||
|
if !ismap {
|
||||||
|
if m, ismap := struct2map(value); !ismap {
|
||||||
|
return "", errors.New("expected map[string]interface{}, struct, or pointer to struct.")
|
||||||
|
} else {
|
||||||
|
return self.Expand(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, p := range self.parts {
|
||||||
|
err := p.expand(&buf, values)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *templatePart) expand(buf *bytes.Buffer, values map[string]interface{}) error {
|
||||||
|
if len(self.raw) > 0 {
|
||||||
|
buf.WriteString(self.raw)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var zeroLen = buf.Len()
|
||||||
|
buf.WriteString(self.first)
|
||||||
|
var firstLen = buf.Len()
|
||||||
|
for _, term := range self.terms {
|
||||||
|
value, exists := values[term.name]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if buf.Len() != firstLen {
|
||||||
|
buf.WriteString(self.sep)
|
||||||
|
}
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
self.expandString(buf, term, v)
|
||||||
|
case []interface{}:
|
||||||
|
self.expandArray(buf, term, v)
|
||||||
|
case map[string]interface{}:
|
||||||
|
if term.truncate > 0 {
|
||||||
|
return errors.New("cannot truncate a map expansion")
|
||||||
|
}
|
||||||
|
self.expandMap(buf, term, v)
|
||||||
|
default:
|
||||||
|
if m, ismap := struct2map(value); ismap {
|
||||||
|
if term.truncate > 0 {
|
||||||
|
return errors.New("cannot truncate a map expansion")
|
||||||
|
}
|
||||||
|
self.expandMap(buf, term, m)
|
||||||
|
} else {
|
||||||
|
str := fmt.Sprintf("%v", value)
|
||||||
|
self.expandString(buf, term, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.Len() == firstLen {
|
||||||
|
original := buf.Bytes()[:zeroLen]
|
||||||
|
buf.Reset()
|
||||||
|
buf.Write(original)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
|
||||||
|
if self.named {
|
||||||
|
buf.WriteString(name)
|
||||||
|
if empty {
|
||||||
|
buf.WriteString(self.ifemp)
|
||||||
|
} else {
|
||||||
|
buf.WriteString("=")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
|
||||||
|
if len(s) > t.truncate && t.truncate > 0 {
|
||||||
|
s = s[:t.truncate]
|
||||||
|
}
|
||||||
|
self.expandName(buf, t.name, len(s) == 0)
|
||||||
|
buf.WriteString(escape(s, self.allowReserved))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return
|
||||||
|
} else if !t.explode {
|
||||||
|
self.expandName(buf, t.name, false)
|
||||||
|
}
|
||||||
|
for i, value := range a {
|
||||||
|
if t.explode && i > 0 {
|
||||||
|
buf.WriteString(self.sep)
|
||||||
|
} else if i > 0 {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
s = v
|
||||||
|
default:
|
||||||
|
s = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
if len(s) > t.truncate && t.truncate > 0 {
|
||||||
|
s = s[:t.truncate]
|
||||||
|
}
|
||||||
|
if self.named && t.explode {
|
||||||
|
self.expandName(buf, t.name, len(s) == 0)
|
||||||
|
}
|
||||||
|
buf.WriteString(escape(s, self.allowReserved))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !t.explode {
|
||||||
|
self.expandName(buf, t.name, len(m) == 0)
|
||||||
|
}
|
||||||
|
var firstLen = buf.Len()
|
||||||
|
for k, value := range m {
|
||||||
|
if firstLen != buf.Len() {
|
||||||
|
if t.explode {
|
||||||
|
buf.WriteString(self.sep)
|
||||||
|
} else {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
s = v
|
||||||
|
default:
|
||||||
|
s = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
if t.explode {
|
||||||
|
buf.WriteString(escape(k, self.allowReserved))
|
||||||
|
buf.WriteRune('=')
|
||||||
|
buf.WriteString(escape(s, self.allowReserved))
|
||||||
|
} else {
|
||||||
|
buf.WriteString(escape(k, self.allowReserved))
|
||||||
|
buf.WriteRune(',')
|
||||||
|
buf.WriteString(escape(s, self.allowReserved))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func struct2map(v interface{}) (map[string]interface{}, bool) {
|
||||||
|
value := reflect.ValueOf(v)
|
||||||
|
switch value.Type().Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return struct2map(value.Elem().Interface())
|
||||||
|
case reflect.Struct:
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
tag := value.Type().Field(i).Tag
|
||||||
|
var name string
|
||||||
|
if strings.Contains(string(tag), ":") {
|
||||||
|
name = tag.Get("uri")
|
||||||
|
} else {
|
||||||
|
name = strings.TrimSpace(string(tag))
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = value.Type().Field(i).Name
|
||||||
|
}
|
||||||
|
m[name] = value.Field(i).Interface()
|
||||||
|
}
|
||||||
|
return m, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
13
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/utils.go
generated
vendored
Normal file
13
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package uritemplates
|
||||||
|
|
||||||
|
func Expand(path string, expansions map[string]string) (string, error) {
|
||||||
|
template, err := Parse(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
values := make(map[string]interface{})
|
||||||
|
for k, v := range expansions {
|
||||||
|
values[k] = v
|
||||||
|
}
|
||||||
|
return template.Expand(values)
|
||||||
|
}
|
38
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/transport/apikey.go
generated
vendored
Normal file
38
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/transport/apikey.go
generated
vendored
Normal file
|
@ -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)
|
||||||
|
}
|
150
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/types.go
generated
vendored
Normal file
150
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/types.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
}
|
44
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/types_test.go
generated
vendored
Normal file
44
Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi/types_test.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// 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"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypes(t *testing.T) {
|
||||||
|
type T struct {
|
||||||
|
I32 Int32s
|
||||||
|
I64 Int64s
|
||||||
|
U32 Uint32s
|
||||||
|
U64 Uint64s
|
||||||
|
F64 Float64s
|
||||||
|
}
|
||||||
|
v := &T{
|
||||||
|
I32: Int32s{-1, 2, 3},
|
||||||
|
I64: Int64s{-1, 2, 1 << 33},
|
||||||
|
U32: Uint32s{1, 2},
|
||||||
|
U64: Uint64s{1, 2, 1 << 33},
|
||||||
|
F64: Float64s{1.5, 3.33},
|
||||||
|
}
|
||||||
|
got, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := `{"I32":["-1","2","3"],"I64":["-1","2","8589934592"],"U32":["1","2"],"U64":["1","2","8589934592"],"F64":["1.5","3.33"]}`
|
||||||
|
if string(got) != want {
|
||||||
|
t.Fatalf("Marshal mismatch.\n got: %s\nwant: %s\n", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 := new(T)
|
||||||
|
if err := json.Unmarshal(got, v2); err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(v, v2) {
|
||||||
|
t.Fatalf("Unmarshal didn't produce same results.\n got: %#v\nwant: %#v\n", v, v2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import (
|
||||||
_ "github.com/docker/machine/drivers/amazonec2"
|
_ "github.com/docker/machine/drivers/amazonec2"
|
||||||
_ "github.com/docker/machine/drivers/azure"
|
_ "github.com/docker/machine/drivers/azure"
|
||||||
_ "github.com/docker/machine/drivers/digitalocean"
|
_ "github.com/docker/machine/drivers/digitalocean"
|
||||||
|
_ "github.com/docker/machine/drivers/googlecomputeengine"
|
||||||
_ "github.com/docker/machine/drivers/none"
|
_ "github.com/docker/machine/drivers/none"
|
||||||
_ "github.com/docker/machine/drivers/virtualbox"
|
_ "github.com/docker/machine/drivers/virtualbox"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
package googlecomputeengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/goauth2/oauth"
|
||||||
|
raw "code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClientId = "22738965389-8arp8bah3uln9eoenproamovfjj1ac33.apps.googleusercontent.com"
|
||||||
|
ClientSecret = "qApc3amTyr5wI74vVrRWAfC_"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newGCEService(storePath string) (*raw.Service, error) {
|
||||||
|
client := newOauthClient(storePath)
|
||||||
|
service, err := raw.New(client)
|
||||||
|
return service, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOauthClient(storePath string) *http.Client {
|
||||||
|
config := &oauth.Config{
|
||||||
|
ClientId: ClientId,
|
||||||
|
ClientSecret: ClientSecret,
|
||||||
|
Scope: raw.ComputeScope,
|
||||||
|
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
token := token(storePath, config)
|
||||||
|
t := oauth.Transport{
|
||||||
|
Token: token,
|
||||||
|
Config: config,
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
}
|
||||||
|
return t.Client()
|
||||||
|
}
|
||||||
|
|
||||||
|
func token(storePath string, config *oauth.Config) *oauth.Token {
|
||||||
|
token, err := tokenFromCache(storePath)
|
||||||
|
if err != nil {
|
||||||
|
token = tokenFromWeb(config)
|
||||||
|
saveToken(storePath, token)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenFromCache(storePath string) (*oauth.Token, error) {
|
||||||
|
tokenPath := path.Join(storePath, "gce_token")
|
||||||
|
f, err := os.Open(tokenPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token := new(oauth.Token)
|
||||||
|
err = gob.NewDecoder(f).Decode(token)
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenFromWeb(config *oauth.Config) *oauth.Token {
|
||||||
|
ch := make(chan string)
|
||||||
|
randState := fmt.Sprintf("st%d", time.Now().UnixNano())
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.URL.Path == "/favicon.ico" {
|
||||||
|
http.Error(rw, "", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.FormValue("state") != randState {
|
||||||
|
log.Debugf("State doesn't match: req = %#v", req)
|
||||||
|
http.Error(rw, "", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if code := req.FormValue("code"); code != "" {
|
||||||
|
fmt.Fprintf(rw, "<h1>Success</h1>Authorized. Code: %v", code)
|
||||||
|
rw.(http.Flusher).Flush()
|
||||||
|
ch <- code
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatalf("no code")
|
||||||
|
http.Error(rw, "", 500)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
config.RedirectURL = ts.URL
|
||||||
|
authURL := config.AuthCodeURL(randState)
|
||||||
|
|
||||||
|
log.Infof("Opening auth URL in browser. If the URL doesn't open, please open it manually and copy the code here.")
|
||||||
|
log.Info(authURL)
|
||||||
|
go openURL(authURL)
|
||||||
|
go getCodeFromStdin(ch)
|
||||||
|
|
||||||
|
code := <-ch
|
||||||
|
log.Infof("Got code: %s", code)
|
||||||
|
|
||||||
|
t := &oauth.Transport{
|
||||||
|
Config: config,
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
}
|
||||||
|
_, err := t.Exchange(code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Token exchange error: %v", err)
|
||||||
|
}
|
||||||
|
return t.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCodeFromStdin(ch chan<- string) {
|
||||||
|
fmt.Print("Enter code: ")
|
||||||
|
var code string
|
||||||
|
fmt.Scanln(&code)
|
||||||
|
code = strings.Trim(code, "\n")
|
||||||
|
// Under 'go test', stdin is /dev/null.
|
||||||
|
if code != "" {
|
||||||
|
ch <- code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openURL(url string) {
|
||||||
|
try := []string{"xdg-open", "google-chrome", "open"}
|
||||||
|
for _, bin := range try {
|
||||||
|
err := exec.Command(bin, url).Run()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToken(storePath string, token *oauth.Token) {
|
||||||
|
tokenPath := path.Join(storePath, "gce_token")
|
||||||
|
log.Infof("Saving token in %v", tokenPath)
|
||||||
|
f, err := os.Create(tokenPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Warning: failed to cache oauth token: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
gob.NewEncoder(f).Encode(token)
|
||||||
|
}
|
|
@ -0,0 +1,288 @@
|
||||||
|
package googlecomputeengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
raw "code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComputeUtil is used to wrap the raw GCE API code and store common parameters.
|
||||||
|
type ComputeUtil struct {
|
||||||
|
zone string
|
||||||
|
instanceName string
|
||||||
|
userName string
|
||||||
|
project string
|
||||||
|
service *raw.Service
|
||||||
|
zoneURL string
|
||||||
|
globalURL string
|
||||||
|
ipAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
apiURL = "https://www.googleapis.com/compute/v1/projects/"
|
||||||
|
imageName = "https://www.googleapis.com/compute/v1/projects/google-containers/global/images/container-vm-v20141016"
|
||||||
|
dockerUrl = "https://bfirsh.s3.amazonaws.com/docker/docker-1.3.1-dev-identity-auth"
|
||||||
|
firewallRule = "docker-machines"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewComputeUtil creates and initializes a ComputeUtil.
|
||||||
|
func newComputeUtil(driver *Driver) (*ComputeUtil, error) {
|
||||||
|
service, err := newGCEService(driver.storePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c := ComputeUtil{
|
||||||
|
zone: driver.Zone,
|
||||||
|
instanceName: driver.InstanceName,
|
||||||
|
userName: driver.UserName,
|
||||||
|
project: driver.Project,
|
||||||
|
service: service,
|
||||||
|
zoneURL: apiURL + driver.Project + "/zones/" + driver.Zone,
|
||||||
|
globalURL: apiURL + driver.Project + "/global",
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) diskName() string {
|
||||||
|
return c.instanceName + "-disk"
|
||||||
|
}
|
||||||
|
|
||||||
|
// disk returns the gce Disk.
|
||||||
|
func (c *ComputeUtil) disk() (*raw.Disk, error) {
|
||||||
|
return c.service.Disks.Get(c.project, c.zone, c.diskName()).Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteDisk deletes the persistent disk.
|
||||||
|
func (c *ComputeUtil) deleteDisk() error {
|
||||||
|
log.Infof("Deleting disk.")
|
||||||
|
op, err := c.service.Disks.Delete(c.project, c.zone, c.diskName()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Waiting for disk to delete.")
|
||||||
|
return c.waitForRegionalOp(op.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) firewallRule() (*raw.Firewall, error) {
|
||||||
|
return c.service.Firewalls.Get(c.project, firewallRule).Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) createFirewallRule() error {
|
||||||
|
log.Infof("Creating firewall rule.")
|
||||||
|
rule := &raw.Firewall{
|
||||||
|
Allowed: []*raw.FirewallAllowed{
|
||||||
|
{
|
||||||
|
IPProtocol: "tcp",
|
||||||
|
Ports: []string{
|
||||||
|
"2376",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SourceRanges: []string{
|
||||||
|
"0.0.0.0/0",
|
||||||
|
},
|
||||||
|
TargetTags: []string{
|
||||||
|
"docker-machine",
|
||||||
|
},
|
||||||
|
Name: firewallRule,
|
||||||
|
}
|
||||||
|
op, err := c.service.Firewalls.Insert(c.project, rule).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.waitForGlobalOp(op.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// instance retrieves the instance.
|
||||||
|
func (c *ComputeUtil) instance() (*raw.Instance, error) {
|
||||||
|
return c.service.Instances.Get(c.project, c.zone, c.instanceName).Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInstance creates a GCE VM instance.
|
||||||
|
func (c *ComputeUtil) createInstance(d *Driver) error {
|
||||||
|
log.Infof("Creating instance.")
|
||||||
|
// The rule will either exist or be nil in case of an error.
|
||||||
|
if rule, _ := c.firewallRule(); rule == nil {
|
||||||
|
if err := c.createFirewallRule(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := &raw.Instance{
|
||||||
|
Name: c.instanceName,
|
||||||
|
Description: "docker host vm",
|
||||||
|
MachineType: c.zoneURL + "/machineTypes/" + d.MachineType,
|
||||||
|
Disks: []*raw.AttachedDisk{
|
||||||
|
{
|
||||||
|
Boot: true,
|
||||||
|
AutoDelete: false,
|
||||||
|
Type: "PERSISTENT",
|
||||||
|
Mode: "READ_WRITE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkInterfaces: []*raw.NetworkInterface{
|
||||||
|
{
|
||||||
|
AccessConfigs: []*raw.AccessConfig{
|
||||||
|
{Type: "ONE_TO_ONE_NAT"},
|
||||||
|
},
|
||||||
|
Network: c.globalURL + "/networks/default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tags: &raw.Tags{
|
||||||
|
Items: []string{
|
||||||
|
"docker-machine",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
disk, err := c.disk()
|
||||||
|
if disk == nil || err != nil {
|
||||||
|
instance.Disks[0].InitializeParams = &raw.AttachedDiskInitializeParams{
|
||||||
|
DiskName: c.diskName(),
|
||||||
|
SourceImage: imageName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance.Disks[0].Source = c.zoneURL + "/disks/" + c.instanceName + "-disk"
|
||||||
|
}
|
||||||
|
op, err := c.service.Instances.Insert(c.project, c.zone, instance).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Waiting for Instance...")
|
||||||
|
if err = c.waitForRegionalOp(op.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err = c.instance()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ip := instance.NetworkInterfaces[0].AccessConfigs[0].NatIP
|
||||||
|
c.waitForSSH(ip)
|
||||||
|
|
||||||
|
// Update the SSH Key
|
||||||
|
sshKey, err := ioutil.ReadFile(d.publicSSHKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Uploading SSH Key")
|
||||||
|
op, err = c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
|
||||||
|
Fingerprint: instance.Metadata.Fingerprint,
|
||||||
|
Items: []*raw.MetadataItems{
|
||||||
|
{
|
||||||
|
Key: "sshKeys",
|
||||||
|
Value: c.userName + ":" + string(sshKey) + "\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Waiting for SSH Key")
|
||||||
|
err = c.waitForRegionalOp(op.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.updateDocker(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteInstance deletes the instance, leaving the persistent disk.
|
||||||
|
func (c *ComputeUtil) deleteInstance() error {
|
||||||
|
log.Infof("Deleting instance.")
|
||||||
|
op, err := c.service.Instances.Delete(c.project, c.zone, c.instanceName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Waiting for instance to delete.")
|
||||||
|
return c.waitForRegionalOp(op.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateDocker updates the docker daemon to the latest version.
|
||||||
|
func (c *ComputeUtil) updateDocker(d *Driver) error {
|
||||||
|
log.Infof("Updating docker.")
|
||||||
|
ip, err := d.GetIP()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving ip: %v", err)
|
||||||
|
}
|
||||||
|
commands := []string{
|
||||||
|
"sudo service docker stop",
|
||||||
|
// The user will need to copy their key to this directory.
|
||||||
|
"sudo mkdir -p /.docker/authorized-keys.d/",
|
||||||
|
fmt.Sprintf("sudo chown -R %v /.docker", d.UserName),
|
||||||
|
// Wait for docker to actually stop before modifying the config.
|
||||||
|
"while [ -e /var/run/docker.pid ]; do sleep 1; done",
|
||||||
|
"sudo sed -i 's/DOCKER_OPTS=.*/DOCKER_OPTS=\"--auth=identity -H unix:\\/\\/\\/var\\/run\\/docker.sock -H 0.0.0.0:2376\"/g' /etc/default/docker",
|
||||||
|
fmt.Sprintf("sudo wget %v -O /usr/bin/docker && sudo chmod +x /usr/bin/docker", dockerUrl)}
|
||||||
|
if err := c.executeCommands(commands, ip, d.sshKeyPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/.docker/authorized-keys.d"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.executeCommands([]string{"sudo service docker start"}, ip, d.sshKeyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) executeCommands(commands []string, ip, sshKeyPath string) error {
|
||||||
|
for _, command := range commands {
|
||||||
|
cmd := ssh.GetSSHCommand(ip, 22, c.userName, sshKeyPath, command)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error executing command: %v %v", command, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
|
||||||
|
for {
|
||||||
|
op, err := opGetter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("operation %q status: %s", op.Name, op.Status)
|
||||||
|
if op.Status == "DONE" {
|
||||||
|
if op.Error != nil {
|
||||||
|
return fmt.Errorf("Operation error: %v", *op.Error.Errors[0])
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForOp waits for the GCE Operation to finish.
|
||||||
|
func (c *ComputeUtil) waitForRegionalOp(name string) error {
|
||||||
|
return c.waitForOp(func() (*raw.Operation, error) {
|
||||||
|
return c.service.ZoneOperations.Get(c.project, c.zone, name).Do()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ComputeUtil) waitForGlobalOp(name string) error {
|
||||||
|
return c.waitForOp(func() (*raw.Operation, error) {
|
||||||
|
return c.service.GlobalOperations.Get(c.project, name).Do()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForSSH waits for SSH to become ready on the instance.
|
||||||
|
func (c *ComputeUtil) waitForSSH(ip string) error {
|
||||||
|
log.Infof("Waiting for SSH...")
|
||||||
|
return ssh.WaitForTCP(fmt.Sprintf("%s:22", ip))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ip retrieves and returns the external IP address of the instance.
|
||||||
|
func (c *ComputeUtil) ip() (string, error) {
|
||||||
|
if c.ipAddress == "" {
|
||||||
|
instance, err := c.service.Instances.Get(c.project, c.zone, c.instanceName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
c.ipAddress = instance.NetworkInterfaces[0].AccessConfigs[0].NatIP
|
||||||
|
}
|
||||||
|
return c.ipAddress, nil
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
package googlecomputeengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/machine/state"
|
||||||
|
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver is a struct compatible with the docker.hosts.drivers.Driver interface.
|
||||||
|
type Driver struct {
|
||||||
|
InstanceName string
|
||||||
|
Zone string
|
||||||
|
MachineType string
|
||||||
|
storePath string
|
||||||
|
UserName string
|
||||||
|
Project string
|
||||||
|
sshKeyPath string
|
||||||
|
publicSSHKeyPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFlags are the command line flags used to create a driver.
|
||||||
|
type CreateFlags struct {
|
||||||
|
InstanceName *string
|
||||||
|
Zone *string
|
||||||
|
MachineType *string
|
||||||
|
UserName *string
|
||||||
|
Project *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
drivers.Register("googlecomputeengine", &drivers.RegisteredDriver{
|
||||||
|
New: NewDriver,
|
||||||
|
GetCreateFlags: GetCreateFlags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCreateFlags registers the flags this driver adds to
|
||||||
|
// "docker hosts create"
|
||||||
|
func GetCreateFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "googlecomputeengine-zone",
|
||||||
|
Usage: "GCE Zone",
|
||||||
|
Value: "us-central1-a",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "googlecomputeengine-machine-type",
|
||||||
|
Usage: "GCE Machine Type",
|
||||||
|
Value: "f1-micro",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "googlecomputeengine-username",
|
||||||
|
Usage: "User Name",
|
||||||
|
Value: "docker-user",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "googlecomputeengine-instance-name",
|
||||||
|
Usage: "GCE Instance Name",
|
||||||
|
Value: "docker-machine",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "googlecomputeengine-project",
|
||||||
|
Usage: "GCE Project",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriver creates a Driver with the specified storePath.
|
||||||
|
func NewDriver(storePath string) (drivers.Driver, error) {
|
||||||
|
return &Driver{storePath: storePath,
|
||||||
|
sshKeyPath: path.Join(storePath, "id_rsa"),
|
||||||
|
publicSSHKeyPath: path.Join(storePath, "id_rsa.pub"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverName returns the name of the driver.
|
||||||
|
func (driver *Driver) DriverName() string {
|
||||||
|
return "googlecomputeengine"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigFromFlags initializes the driver based on the command line flags.
|
||||||
|
func (driver *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||||
|
driver.InstanceName = flags.String("googlecomputeengine-instance-name")
|
||||||
|
driver.Zone = flags.String("googlecomputeengine-zone")
|
||||||
|
driver.MachineType = flags.String("googlecomputeengine-machine-type")
|
||||||
|
driver.UserName = flags.String("googlecomputeengine-username")
|
||||||
|
driver.Project = flags.String("googlecomputeengine-project")
|
||||||
|
if driver.Project == "" {
|
||||||
|
return fmt.Errorf("Please specify the Google Cloud Project name using the option --googlecomputeengine-project.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *Driver) initApis() (*ComputeUtil, error) {
|
||||||
|
return newComputeUtil(driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a GCE VM instance acting as a docker host.
|
||||||
|
func (driver *Driver) Create() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Creating host...")
|
||||||
|
// Check if the instance already exists. There will be an error if the instance
|
||||||
|
// doesn't exist, so just check instance for nil.
|
||||||
|
if instance, _ := c.instance(); instance != nil {
|
||||||
|
return fmt.Errorf("Instance %v already exists.", driver.InstanceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Generating SSH Key")
|
||||||
|
if err := ssh.GenerateSSHKey(driver.sshKeyPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.createInstance(driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURL returns the URL of the remote docker daemon.
|
||||||
|
func (driver *Driver) GetURL() (string, error) {
|
||||||
|
ip, err := driver.GetIP()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("tcp://%s:2376", ip)
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIP returns the IP address of the GCE instance.
|
||||||
|
func (driver *Driver) GetIP() (string, error) {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return c.ip()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns a docker.hosts.state.State value representing the current state of the host.
|
||||||
|
func (driver *Driver) GetState() (state.State, error) {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return state.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// All we care about is whether the disk exists, so we just check disk for a nil value.
|
||||||
|
// There will be no error if disk is not nil.
|
||||||
|
disk, _ := c.disk()
|
||||||
|
instance, _ := c.instance()
|
||||||
|
if instance == nil && disk == nil {
|
||||||
|
return state.None, nil
|
||||||
|
}
|
||||||
|
if instance == nil && disk != nil {
|
||||||
|
return state.Stopped, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch instance.Status {
|
||||||
|
case "PROVISIONING", "STAGING":
|
||||||
|
return state.Starting, nil
|
||||||
|
case "RUNNING":
|
||||||
|
return state.Running, nil
|
||||||
|
case "STOPPING", "STOPPED", "TERMINATED":
|
||||||
|
return state.Stopped, nil
|
||||||
|
}
|
||||||
|
return state.None, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creates a GCE instance and attaches it to the existing disk.
|
||||||
|
func (driver *Driver) Start() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.createInstance(driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop deletes the GCE instance, but keeps the disk.
|
||||||
|
func (driver *Driver) Stop() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.deleteInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes the GCE instance and the disk.
|
||||||
|
func (driver *Driver) Remove() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := driver.GetState()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == state.Running {
|
||||||
|
if err := c.deleteInstance(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.deleteDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart deletes and recreates the GCE instance, keeping the disk.
|
||||||
|
func (driver *Driver) Restart() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.deleteInstance(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.createInstance(driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill deletes the GCE instance, but keeps the disk.
|
||||||
|
func (driver *Driver) Kill() error {
|
||||||
|
return driver.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSSHCommand returns a command that will run over SSH on the GCE instance.
|
||||||
|
func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
|
||||||
|
ip, err := driver.GetIP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ssh.GetSSHCommand(ip, 22, driver.UserName, driver.sshKeyPath, args...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrades the docker daemon on the host to the latest version.
|
||||||
|
func (driver *Driver) Upgrade() error {
|
||||||
|
c, err := newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.updateDocker(driver)
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package googlecomputeengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/docker/machine/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
project = flag.String("project", "", "Project")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
driver *Driver
|
||||||
|
c *ComputeUtil
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
zone = "us-central1-a"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *project == "" {
|
||||||
|
log.Error("You must specify a GCE project using the --project flag. All tests will be skipped.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver = &Driver{
|
||||||
|
storePath: tmpDir,
|
||||||
|
InstanceName: "test-instance",
|
||||||
|
Zone: "us-central1-a",
|
||||||
|
MachineType: "n1-standard-1",
|
||||||
|
UserName: os.Getenv("USER"),
|
||||||
|
Project: *project,
|
||||||
|
sshKeyPath: path.Join(tmpDir, "id_rsa"),
|
||||||
|
publicSSHKeyPath: path.Join(tmpDir, "id_rsa.pub"),
|
||||||
|
}
|
||||||
|
c, err = newComputeUtil(driver)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupDisk() {
|
||||||
|
log.Println("Cleaning up disk.")
|
||||||
|
d, err := c.service.Disks.Get(*project, zone, "test-instance-disk").Do()
|
||||||
|
if d == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
op, err := c.service.Disks.Delete(*project, zone, "test-instance-disk").Do()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error cleaning up disk: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.waitForRegionalOp(op.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error cleaning up disk: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupInstance() {
|
||||||
|
log.Println("Cleaning up instance.")
|
||||||
|
d, err := c.service.Instances.Get(*project, zone, "test-instance").Do()
|
||||||
|
if d == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
op, err := c.service.Instances.Delete(*project, zone, "test-instance").Do()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error cleaning up instance: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.waitForRegionalOp(op.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error cleaning up instance: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
cleanupInstance()
|
||||||
|
cleanupDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
type operation struct {
|
||||||
|
Name string
|
||||||
|
Method func() error
|
||||||
|
DiskExpected bool
|
||||||
|
InstanceExpected bool
|
||||||
|
State state.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicOperations(t *testing.T) {
|
||||||
|
if *project == "" {
|
||||||
|
t.Skip("Skipping tests because no --project flag was passed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ops := []operation{
|
||||||
|
{
|
||||||
|
Name: "Create",
|
||||||
|
Method: driver.Create,
|
||||||
|
DiskExpected: true,
|
||||||
|
InstanceExpected: true,
|
||||||
|
State: state.Running,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Stop",
|
||||||
|
Method: driver.Stop,
|
||||||
|
DiskExpected: true,
|
||||||
|
InstanceExpected: false,
|
||||||
|
State: state.Stopped,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Start",
|
||||||
|
Method: driver.Start,
|
||||||
|
DiskExpected: true,
|
||||||
|
InstanceExpected: true,
|
||||||
|
State: state.Running,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Restart",
|
||||||
|
Method: driver.Restart,
|
||||||
|
DiskExpected: true,
|
||||||
|
InstanceExpected: true,
|
||||||
|
State: state.Running,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Remove",
|
||||||
|
Method: driver.Remove,
|
||||||
|
DiskExpected: false,
|
||||||
|
InstanceExpected: false,
|
||||||
|
State: state.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
for _, op := range ops {
|
||||||
|
log.Info("Executing operation: ", op.Name)
|
||||||
|
err := op.Method()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
AssertDiskAndInstance(op.DiskExpected, op.InstanceExpected)
|
||||||
|
if s, _ := driver.GetState(); s != op.State {
|
||||||
|
t.Fatalf("State should be %v, but is: %v", op.State, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertDiskAndInstance(diskShouldExist, instShouldExist bool) {
|
||||||
|
d, err := c.service.Disks.Get(*project, zone, "test-instance-disk").Do()
|
||||||
|
if diskShouldExist {
|
||||||
|
if d == nil || err != nil {
|
||||||
|
log.Fatal("Error retrieving disk that should exist.")
|
||||||
|
}
|
||||||
|
} else if d != nil {
|
||||||
|
log.Fatal("Disk shouldn't exist but does.")
|
||||||
|
}
|
||||||
|
i, err := c.service.Instances.Get(*project, zone, "test-instance").Do()
|
||||||
|
if instShouldExist {
|
||||||
|
if i == nil || err != nil {
|
||||||
|
log.Fatal("error retrieving instance that should exist.")
|
||||||
|
}
|
||||||
|
} else if i != nil {
|
||||||
|
log.Fatal("Instance shouldnt exist but does.")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue