mirror of
https://github.com/docker/docs.git
synced 2026-11-05 00:14:17 +03:00
Merge branch 'master' into enhanced-vmwarefusion-driver
This commit is contained in:
8
Godeps/Godeps.json
generated
8
Godeps/Godeps.json
generated
@@ -147,13 +147,17 @@
|
||||
"ImportPath": "golang.org/x/crypto/ssh",
|
||||
"Rev": "1fbbd62cfec66bd39d91e97749579579d4d3037e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/compute/v1",
|
||||
"Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5"
|
||||
"Rev": "a09229c13c2f13bbdedf7b31b506cad4c83ef3bf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/googleapi",
|
||||
"Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5"
|
||||
"Rev": "a09229c13c2f13bbdedf7b31b506cad4c83ef3bf"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
||||
// 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 context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface {
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out <-chan Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled")
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int
|
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *emptyCtx) String() string {
|
||||
switch e {
|
||||
case background:
|
||||
return "context.Background"
|
||||
case todo:
|
||||
return "context.TODO"
|
||||
}
|
||||
return "unknown empty Context"
|
||||
}
|
||||
|
||||
var (
|
||||
background = new(emptyCtx)
|
||||
todo = new(emptyCtx)
|
||||
)
|
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
func Background() Context {
|
||||
return background
|
||||
}
|
||||
|
||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it's is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
func TODO() Context {
|
||||
return todo
|
||||
}
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func()
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
c := newCancelCtx(parent)
|
||||
propagateCancel(parent, &c)
|
||||
return &c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) cancelCtx {
|
||||
return cancelCtx{
|
||||
Context: parent,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) {
|
||||
if parent.Done() == nil {
|
||||
return // parent is never canceled
|
||||
}
|
||||
if p, ok := parentCancelCtx(parent); ok {
|
||||
p.mu.Lock()
|
||||
if p.err != nil {
|
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err)
|
||||
} else {
|
||||
if p.children == nil {
|
||||
p.children = make(map[canceler]bool)
|
||||
}
|
||||
p.children[child] = true
|
||||
}
|
||||
p.mu.Unlock()
|
||||
} else {
|
||||
go func() {
|
||||
select {
|
||||
case <-parent.Done():
|
||||
child.cancel(false, parent.Err())
|
||||
case <-child.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||
for {
|
||||
switch c := parent.(type) {
|
||||
case *cancelCtx:
|
||||
return c, true
|
||||
case *timerCtx:
|
||||
return &c.cancelCtx, true
|
||||
case *valueCtx:
|
||||
parent = c.Context
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) {
|
||||
p, ok := parentCancelCtx(parent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.children != nil {
|
||||
delete(p.children, child)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface {
|
||||
cancel(removeFromParent bool, err error)
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct {
|
||||
Context
|
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex
|
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} {
|
||||
return c.done
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Err() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *cancelCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||
}
|
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||
if err == nil {
|
||||
panic("context: internal error: missing cancel error")
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
c.mu.Unlock()
|
||||
return // already canceled
|
||||
}
|
||||
c.err = err
|
||||
close(c.done)
|
||||
for child := range c.children {
|
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err)
|
||||
}
|
||||
c.children = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if removeFromParent {
|
||||
removeChild(c.Context, c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent)
|
||||
}
|
||||
c := &timerCtx{
|
||||
cancelCtx: newCancelCtx(parent),
|
||||
deadline: deadline,
|
||||
}
|
||||
propagateCancel(parent, c)
|
||||
d := deadline.Sub(time.Now())
|
||||
if d <= 0 {
|
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.err == nil {
|
||||
c.timer = time.AfterFunc(d, func() {
|
||||
c.cancel(true, DeadlineExceeded)
|
||||
})
|
||||
}
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct {
|
||||
cancelCtx
|
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return c.deadline, true
|
||||
}
|
||||
|
||||
func (c *timerCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||
}
|
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||
c.cancelCtx.cancel(false, err)
|
||||
if removeFromParent {
|
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c)
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return &valueCtx{parent, key, val}
|
||||
}
|
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct {
|
||||
Context
|
||||
key, val interface{}
|
||||
}
|
||||
|
||||
func (c *valueCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||
}
|
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||
if c.key == key {
|
||||
return c.val
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
||||
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,575 @@
|
||||
// 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 context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// otherContext is a Context that's not one of the types defined in context.go.
|
||||
// This lets us test code paths that differ based on the underlying type of the
|
||||
// Context.
|
||||
type otherContext struct {
|
||||
Context
|
||||
}
|
||||
|
||||
func TestBackground(t *testing.T) {
|
||||
c := Background()
|
||||
if c == nil {
|
||||
t.Fatalf("Background returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
||||
t.Errorf("Background().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTODO(t *testing.T) {
|
||||
c := TODO()
|
||||
if c == nil {
|
||||
t.Fatalf("TODO returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
||||
t.Errorf("TODO().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithCancel(t *testing.T) {
|
||||
c1, cancel := WithCancel(Background())
|
||||
|
||||
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
||||
t.Errorf("c1.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
o := otherContext{c1}
|
||||
c2, _ := WithCancel(o)
|
||||
contexts := []Context{c1, o, c2}
|
||||
|
||||
for i, c := range contexts {
|
||||
if d := c.Done(); d == nil {
|
||||
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
||||
}
|
||||
if e := c.Err(); e != nil {
|
||||
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
||||
}
|
||||
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||
|
||||
for i, c := range contexts {
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentFinishesChild(t *testing.T) {
|
||||
// Context tree:
|
||||
// parent -> cancelChild
|
||||
// parent -> valueChild -> timerChild
|
||||
parent, cancel := WithCancel(Background())
|
||||
cancelChild, stop := WithCancel(parent)
|
||||
defer stop()
|
||||
valueChild := WithValue(parent, "key", "value")
|
||||
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
|
||||
defer stop()
|
||||
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-cancelChild.Done():
|
||||
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-timerChild.Done():
|
||||
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-valueChild.Done():
|
||||
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
|
||||
// The parent's children should contain the two cancelable children.
|
||||
pc := parent.(*cancelCtx)
|
||||
cc := cancelChild.(*cancelCtx)
|
||||
tc := timerChild.(*timerCtx)
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
|
||||
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
|
||||
pc.children, cc, tc)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
|
||||
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
|
||||
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||
}
|
||||
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
|
||||
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 0 {
|
||||
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
|
||||
// parent and children should all be finished.
|
||||
check := func(ctx Context, name string) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
|
||||
}
|
||||
if e := ctx.Err(); e != Canceled {
|
||||
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
|
||||
}
|
||||
}
|
||||
check(parent, "parent")
|
||||
check(cancelChild, "cancelChild")
|
||||
check(valueChild, "valueChild")
|
||||
check(timerChild, "timerChild")
|
||||
|
||||
// WithCancel should return a canceled context on a canceled parent.
|
||||
precanceledChild := WithValue(parent, "key", "value")
|
||||
select {
|
||||
case <-precanceledChild.Done():
|
||||
default:
|
||||
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := precanceledChild.Err(); e != Canceled {
|
||||
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildFinishesFirst(t *testing.T) {
|
||||
cancelable, stop := WithCancel(Background())
|
||||
defer stop()
|
||||
for _, parent := range []Context{Background(), cancelable} {
|
||||
child, cancel := WithCancel(parent)
|
||||
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-child.Done():
|
||||
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
|
||||
cc := child.(*cancelCtx)
|
||||
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
|
||||
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
|
||||
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
|
||||
}
|
||||
|
||||
if pcok {
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 1 || !pc.children[cc] {
|
||||
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
if pcok {
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 0 {
|
||||
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
}
|
||||
|
||||
// child should be finished.
|
||||
select {
|
||||
case <-child.Done():
|
||||
default:
|
||||
t.Errorf("<-child.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := child.Err(); e != Canceled {
|
||||
t.Errorf("child.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
|
||||
// parent should not be finished.
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if e := parent.Err(); e != nil {
|
||||
t.Errorf("parent.Err() == %v want nil", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDeadline(c Context, wait time.Duration, t *testing.T) {
|
||||
select {
|
||||
case <-time.After(wait):
|
||||
t.Fatalf("context should have timed out")
|
||||
case <-c.Done():
|
||||
}
|
||||
if e := c.Err(); e != DeadlineExceeded {
|
||||
t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeadline(t *testing.T) {
|
||||
c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
o := otherContext{c}
|
||||
testDeadline(o, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
o = otherContext{c}
|
||||
c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond))
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
c, _ := WithTimeout(Background(), 100*time.Millisecond)
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||
o := otherContext{c}
|
||||
testDeadline(o, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||
o = otherContext{c}
|
||||
c, _ = WithTimeout(o, 300*time.Millisecond)
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
}
|
||||
|
||||
func TestCanceledTimeout(t *testing.T) {
|
||||
c, _ := WithTimeout(Background(), 200*time.Millisecond)
|
||||
o := otherContext{c}
|
||||
c, cancel := WithTimeout(o, 400*time.Millisecond)
|
||||
cancel()
|
||||
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
type key1 int
|
||||
type key2 int
|
||||
|
||||
var k1 = key1(1)
|
||||
var k2 = key2(1) // same int as k1, different type
|
||||
var k3 = key2(3) // same type as k2, different int
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
check := func(c Context, nm, v1, v2, v3 string) {
|
||||
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
||||
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
||||
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
||||
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
c0 := Background()
|
||||
check(c0, "c0", "", "", "")
|
||||
|
||||
c1 := WithValue(Background(), k1, "c1k1")
|
||||
check(c1, "c1", "c1k1", "", "")
|
||||
|
||||
if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
|
||||
t.Errorf("c.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
c2 := WithValue(c1, k2, "c2k2")
|
||||
check(c2, "c2", "c1k1", "c2k2", "")
|
||||
|
||||
c3 := WithValue(c2, k3, "c3k3")
|
||||
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
||||
|
||||
c4 := WithValue(c3, k1, nil)
|
||||
check(c4, "c4", "", "c2k2", "c3k3")
|
||||
|
||||
o0 := otherContext{Background()}
|
||||
check(o0, "o0", "", "", "")
|
||||
|
||||
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
||||
check(o1, "o1", "c1k1", "", "")
|
||||
|
||||
o2 := WithValue(o1, k2, "o2k2")
|
||||
check(o2, "o2", "c1k1", "o2k2", "")
|
||||
|
||||
o3 := otherContext{c4}
|
||||
check(o3, "o3", "", "c2k2", "c3k3")
|
||||
|
||||
o4 := WithValue(o3, k3, nil)
|
||||
check(o4, "o4", "", "c2k2", "")
|
||||
}
|
||||
|
||||
func TestAllocs(t *testing.T) {
|
||||
bg := Background()
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func()
|
||||
limit float64
|
||||
gccgoLimit float64
|
||||
}{
|
||||
{
|
||||
desc: "Background()",
|
||||
f: func() { Background() },
|
||||
limit: 0,
|
||||
gccgoLimit: 0,
|
||||
},
|
||||
{
|
||||
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
||||
f: func() {
|
||||
c := WithValue(bg, k1, nil)
|
||||
c.Value(k1)
|
||||
},
|
||||
limit: 3,
|
||||
gccgoLimit: 3,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 15*time.Millisecond)",
|
||||
f: func() {
|
||||
c, _ := WithTimeout(bg, 15*time.Millisecond)
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 13,
|
||||
},
|
||||
{
|
||||
desc: "WithCancel(bg)",
|
||||
f: func() {
|
||||
c, cancel := WithCancel(bg)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 5,
|
||||
gccgoLimit: 8,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 100*time.Millisecond)",
|
||||
f: func() {
|
||||
c, cancel := WithTimeout(bg, 100*time.Millisecond)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 25,
|
||||
},
|
||||
} {
|
||||
limit := test.limit
|
||||
if runtime.Compiler == "gccgo" {
|
||||
// gccgo does not yet do escape analysis.
|
||||
// TOOD(iant): Remove this when gccgo does do escape analysis.
|
||||
limit = test.gccgoLimit
|
||||
}
|
||||
if n := testing.AllocsPerRun(100, test.f); n > limit {
|
||||
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimultaneousCancels(t *testing.T) {
|
||||
root, cancel := WithCancel(Background())
|
||||
m := map[Context]CancelFunc{root: cancel}
|
||||
q := []Context{root}
|
||||
// Create a tree of contexts.
|
||||
for len(q) != 0 && len(m) < 100 {
|
||||
parent := q[0]
|
||||
q = q[1:]
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx, cancel := WithCancel(parent)
|
||||
m[ctx] = cancel
|
||||
q = append(q, ctx)
|
||||
}
|
||||
}
|
||||
// Start all the cancels in a random order.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(m))
|
||||
for _, cancel := range m {
|
||||
go func(cancel CancelFunc) {
|
||||
cancel()
|
||||
wg.Done()
|
||||
}(cancel)
|
||||
}
|
||||
// Wait on all the contexts in a random order.
|
||||
for ctx := range m {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
// Wait for all the cancel functions to return.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterlockedCancels(t *testing.T) {
|
||||
parent, cancelParent := WithCancel(Background())
|
||||
child, cancelChild := WithCancel(parent)
|
||||
go func() {
|
||||
parent.Done()
|
||||
cancelChild()
|
||||
}()
|
||||
cancelParent()
|
||||
select {
|
||||
case <-child.Done():
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayersCancel(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), false)
|
||||
}
|
||||
|
||||
func TestLayersTimeout(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), true)
|
||||
}
|
||||
|
||||
func testLayers(t *testing.T, seed int64, testTimeout bool) {
|
||||
rand.Seed(seed)
|
||||
errorf := func(format string, a ...interface{}) {
|
||||
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
||||
}
|
||||
const (
|
||||
timeout = 200 * time.Millisecond
|
||||
minLayers = 30
|
||||
)
|
||||
type value int
|
||||
var (
|
||||
vals []*value
|
||||
cancels []CancelFunc
|
||||
numTimers int
|
||||
ctx = Background()
|
||||
)
|
||||
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
v := new(value)
|
||||
ctx = WithValue(ctx, v, v)
|
||||
vals = append(vals, v)
|
||||
case 1:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithCancel(ctx)
|
||||
cancels = append(cancels, cancel)
|
||||
case 2:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithTimeout(ctx, timeout)
|
||||
cancels = append(cancels, cancel)
|
||||
numTimers++
|
||||
}
|
||||
}
|
||||
checkValues := func(when string) {
|
||||
for _, key := range vals {
|
||||
if val := ctx.Value(key).(*value); key != val {
|
||||
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errorf("ctx should not be canceled yet")
|
||||
default:
|
||||
}
|
||||
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
||||
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
||||
}
|
||||
t.Log(ctx)
|
||||
checkValues("before cancel")
|
||||
if testTimeout {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(timeout + timeout/10):
|
||||
errorf("ctx should have timed out")
|
||||
}
|
||||
checkValues("after timeout")
|
||||
} else {
|
||||
cancel := cancels[rand.Intn(len(cancels))]
|
||||
cancel()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
errorf("ctx should be canceled")
|
||||
}
|
||||
checkValues("after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelRemoves(t *testing.T) {
|
||||
checkChildren := func(when string, ctx Context, want int) {
|
||||
if got := len(ctx.(*cancelCtx).children); got != want {
|
||||
t.Errorf("%s: context has %d children, want %d", when, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, _ := WithCancel(Background())
|
||||
checkChildren("after creation", ctx, 0)
|
||||
_, cancel := WithCancel(ctx)
|
||||
checkChildren("with WithCancel child ", ctx, 1)
|
||||
cancel()
|
||||
checkChildren("after cancelling WithCancel child", ctx, 0)
|
||||
|
||||
ctx, _ = WithCancel(Background())
|
||||
checkChildren("after creation", ctx, 0)
|
||||
_, cancel = WithTimeout(ctx, 60*time.Minute)
|
||||
checkChildren("with WithTimeout child ", ctx, 1)
|
||||
cancel()
|
||||
checkChildren("after cancelling WithTimeout child", ctx, 0)
|
||||
}
|
||||
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 context_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func ExampleWithTimeout() {
|
||||
// Pass a context with a timeout to tell a blocking function that it
|
||||
// should abandon its work after the timeout elapses.
|
||||
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
fmt.Println("overslept")
|
||||
case <-ctx.Done():
|
||||
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
|
||||
}
|
||||
// Output:
|
||||
// context deadline exceeded
|
||||
}
|
||||
2544
Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json
generated
vendored
2544
Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json
generated
vendored
File diff suppressed because it is too large
Load Diff
4959
Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go
generated
vendored
4959
Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go
generated
vendored
File diff suppressed because it is too large
Load Diff
269
Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi.go
generated
vendored
269
Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi.go
generated
vendored
@@ -9,6 +9,7 @@ package googleapi
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -16,9 +17,13 @@ import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi/internal/uritemplates"
|
||||
)
|
||||
|
||||
@@ -30,7 +35,25 @@ type ContentTyper interface {
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
const Version = "0.5"
|
||||
// A SizeReaderAt is a ReaderAt with a Size method.
|
||||
// An io.SectionReader implements SizeReaderAt.
|
||||
type SizeReaderAt interface {
|
||||
io.ReaderAt
|
||||
Size() int64
|
||||
}
|
||||
|
||||
const (
|
||||
Version = "0.5"
|
||||
|
||||
// statusResumeIncomplete is the code returned by the Google uploader when the transfer is not yet complete.
|
||||
statusResumeIncomplete = 308
|
||||
|
||||
// UserAgent is the header string used to identify this package.
|
||||
UserAgent = "google-api-go-client/" + Version
|
||||
|
||||
// uploadPause determines the delay between failed upload attempts
|
||||
uploadPause = 1 * time.Second
|
||||
)
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
@@ -130,14 +153,41 @@ func getMediaType(media io.Reader) (io.Reader, string) {
|
||||
return media, typer.ContentType()
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
typ := "application/octet-stream"
|
||||
buf, err := ioutil.ReadAll(io.LimitReader(media, 512))
|
||||
if err != nil {
|
||||
pw.CloseWithError(fmt.Errorf("error reading media: %v", err))
|
||||
return pr, typ
|
||||
}
|
||||
typ = http.DetectContentType(buf)
|
||||
mr := io.MultiReader(bytes.NewReader(buf), media)
|
||||
go func() {
|
||||
_, err = io.Copy(pw, mr)
|
||||
if err != nil {
|
||||
pw.CloseWithError(fmt.Errorf("error reading media: %v", err))
|
||||
return
|
||||
}
|
||||
pw.Close()
|
||||
}()
|
||||
return pr, typ
|
||||
}
|
||||
|
||||
// DetectMediaType detects and returns the content type of the provided media.
|
||||
// If the type can not be determined, "application/octet-stream" is returned.
|
||||
func DetectMediaType(media io.ReaderAt) string {
|
||||
if typer, ok := media.(ContentTyper); ok {
|
||||
return typer.ContentType()
|
||||
}
|
||||
|
||||
typ := "application/octet-stream"
|
||||
buf := make([]byte, 1024)
|
||||
n, err := media.Read(buf)
|
||||
n, err := media.ReadAt(buf, 0)
|
||||
buf = buf[:n]
|
||||
if err == nil {
|
||||
if err == nil || err == io.EOF {
|
||||
typ = http.DetectContentType(buf)
|
||||
}
|
||||
return io.MultiReader(bytes.NewBuffer(buf), media), typ
|
||||
return typ
|
||||
}
|
||||
|
||||
type Lengther interface {
|
||||
@@ -145,7 +195,7 @@ type Lengther interface {
|
||||
}
|
||||
|
||||
// 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.
|
||||
// final error from r is io.EOF and e is non-nil, e is used instead.
|
||||
type endingWithErrorReader struct {
|
||||
r io.Reader
|
||||
e error
|
||||
@@ -159,38 +209,6 @@ func (er endingWithErrorReader) Read(p []byte) (n int, err error) {
|
||||
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)
|
||||
@@ -219,60 +237,185 @@ func (w countingWriter) Write(p []byte) (int, error) {
|
||||
// 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) {
|
||||
func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (cancel func(), 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.
|
||||
// Get the media type, which might return a different reader instance.
|
||||
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)
|
||||
media, mediaType = getMediaType(media)
|
||||
|
||||
// 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()
|
||||
}
|
||||
body, bodyType := *bodyp, *ctypep
|
||||
|
||||
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 {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: body CreatePart failed: %v", err))
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(w, body)
|
||||
if err != nil {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: body Copy failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
w, err = mpw.CreatePart(typeHeader(mediaType))
|
||||
if err != nil {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: media CreatePart failed: %v", err))
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(w, media)
|
||||
if err != nil {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: media Copy failed: %v", err))
|
||||
return
|
||||
}
|
||||
mpw.Close()
|
||||
pw.Close()
|
||||
}()
|
||||
return totalContentLength, true
|
||||
cancel = func() { pw.CloseWithError(errAborted) }
|
||||
return cancel, true
|
||||
}
|
||||
|
||||
var errAborted = errors.New("googleapi: upload aborted")
|
||||
|
||||
// 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)
|
||||
|
||||
// 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 io.ReaderAt
|
||||
// MediaType defines the media type, e.g. "image/jpeg".
|
||||
MediaType string
|
||||
// ContentLength is the full size of the object being uploaded.
|
||||
ContentLength int64
|
||||
|
||||
mu sync.Mutex // guards progress
|
||||
progress int64 // number of bytes uploaded so far
|
||||
|
||||
// Callback is an optional function that will be called upon every progress update.
|
||||
Callback ProgressUpdater
|
||||
}
|
||||
|
||||
var (
|
||||
// rangeRE matches the transfer status response from the server. $1 is the last byte index uploaded.
|
||||
rangeRE = regexp.MustCompile(`^bytes=0\-(\d+)$`)
|
||||
// chunkSize is the size of the chunks created during a resumable upload and should be a power of two.
|
||||
// 1<<18 is the minimum size supported by the Google uploader, and there is no maximum.
|
||||
chunkSize int64 = 1 << 18
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (rx *ResumableUpload) transferStatus() (int64, *http.Response, error) {
|
||||
req, _ := http.NewRequest("POST", rx.URI, nil)
|
||||
req.ContentLength = 0
|
||||
req.Header.Set("User-Agent", rx.UserAgent)
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes */%v", rx.ContentLength))
|
||||
res, err := rx.Client.Do(req)
|
||||
if err != nil || res.StatusCode != statusResumeIncomplete {
|
||||
return 0, res, err
|
||||
}
|
||||
var start int64
|
||||
if m := rangeRE.FindStringSubmatch(res.Header.Get("Range")); len(m) == 2 {
|
||||
start, err = strconv.ParseInt(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("unable to parse range size %v", m[1])
|
||||
}
|
||||
start += 1 // Start at the next byte
|
||||
}
|
||||
return start, res, nil
|
||||
}
|
||||
|
||||
type chunk struct {
|
||||
body io.Reader
|
||||
size int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (rx *ResumableUpload) transferChunks(ctx context.Context) (*http.Response, error) {
|
||||
start, res, err := rx.transferStatus()
|
||||
if err != nil || res.StatusCode != statusResumeIncomplete {
|
||||
return res, err
|
||||
}
|
||||
|
||||
for {
|
||||
select { // Check for cancellation
|
||||
case <-ctx.Done():
|
||||
res.StatusCode = http.StatusRequestTimeout
|
||||
return res, ctx.Err()
|
||||
default:
|
||||
}
|
||||
reqSize := rx.ContentLength - start
|
||||
if reqSize > chunkSize {
|
||||
reqSize = chunkSize
|
||||
}
|
||||
r := io.NewSectionReader(rx.Media, start, reqSize)
|
||||
req, _ := http.NewRequest("POST", rx.URI, r)
|
||||
req.ContentLength = reqSize
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %v-%v/%v", start, start+reqSize-1, rx.ContentLength))
|
||||
req.Header.Set("Content-Type", rx.MediaType)
|
||||
req.Header.Set("User-Agent", rx.UserAgent)
|
||||
res, err = rx.Client.Do(req)
|
||||
start += reqSize
|
||||
if err == nil && (res.StatusCode == statusResumeIncomplete || res.StatusCode == http.StatusOK) {
|
||||
rx.mu.Lock()
|
||||
rx.progress = start // keep track of number of bytes sent so far
|
||||
rx.mu.Unlock()
|
||||
if rx.Callback != nil {
|
||||
rx.Callback(start, rx.ContentLength)
|
||||
}
|
||||
}
|
||||
if err != nil || res.StatusCode != statusResumeIncomplete {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
var sleep = time.Sleep // override in unit tests
|
||||
|
||||
// Upload starts the process of a resumable upload with a cancellable context.
|
||||
// It retries indefinitely (with a pause of uploadPause between attempts) until cancelled.
|
||||
// It is called from the auto-generated API code and is not visible to the user.
|
||||
// rx is private to the auto-generated API code.
|
||||
func (rx *ResumableUpload) Upload(ctx context.Context) (*http.Response, error) {
|
||||
var res *http.Response
|
||||
var err error
|
||||
for {
|
||||
res, err = rx.transferChunks(ctx)
|
||||
if err != nil || res.StatusCode == http.StatusCreated || res.StatusCode == http.StatusOK {
|
||||
return res, err
|
||||
}
|
||||
select { // Check for cancellation
|
||||
case <-ctx.Done():
|
||||
res.StatusCode = http.StatusRequestTimeout
|
||||
return res, ctx.Err()
|
||||
default:
|
||||
}
|
||||
sleep(uploadPause)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func ResolveRelative(basestr, relstr string) string {
|
||||
|
||||
237
Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go
generated
vendored
237
Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go
generated
vendored
@@ -11,9 +11,15 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type SetOpaqueTest struct {
|
||||
@@ -359,3 +365,234 @@ func TestConvertVariant(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type unexpectedReader struct{}
|
||||
|
||||
func (unexpectedReader) Read([]byte) (int, error) {
|
||||
return 0, fmt.Errorf("unexpected read in test.")
|
||||
}
|
||||
|
||||
var contentRangeRE = regexp.MustCompile(`^bytes (\d+)\-(\d+)/(\d+)$`)
|
||||
|
||||
func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
t.req = req
|
||||
if rng := req.Header.Get("Content-Range"); rng != "" && !strings.HasPrefix(rng, "bytes */") { // Read the data
|
||||
m := contentRangeRE.FindStringSubmatch(rng)
|
||||
if len(m) != 4 {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
start, err := strconv.ParseInt(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
end, err := strconv.ParseInt(m[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
totalSize, err := strconv.ParseInt(m[3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
partialSize := end - start + 1
|
||||
t.buf, err = ioutil.ReadAll(req.Body)
|
||||
if err != nil || int64(len(t.buf)) != partialSize {
|
||||
return nil, fmt.Errorf("unable to read %v bytes from request data, n=%v: %v", partialSize, len(t.buf), err)
|
||||
}
|
||||
if totalSize == end+1 {
|
||||
t.statusCode = 200 // signify completion of transfer
|
||||
}
|
||||
}
|
||||
f := ioutil.NopCloser(unexpectedReader{})
|
||||
res := &http.Response{
|
||||
Body: f,
|
||||
StatusCode: t.statusCode,
|
||||
Header: http.Header{},
|
||||
}
|
||||
if t.rangeVal != "" {
|
||||
res.Header.Set("Range", t.rangeVal)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type testTransport struct {
|
||||
req *http.Request
|
||||
statusCode int
|
||||
rangeVal string
|
||||
want int64
|
||||
buf []byte
|
||||
}
|
||||
|
||||
var statusTests = []*testTransport{
|
||||
&testTransport{statusCode: 308, want: 0},
|
||||
&testTransport{statusCode: 308, rangeVal: "bytes=0-0", want: 1},
|
||||
&testTransport{statusCode: 308, rangeVal: "bytes=0-42", want: 43},
|
||||
}
|
||||
|
||||
func TestTransferStatus(t *testing.T) {
|
||||
for _, tr := range statusTests {
|
||||
rx := &ResumableUpload{
|
||||
Client: &http.Client{Transport: tr},
|
||||
}
|
||||
g, _, err := rx.transferStatus()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if g != tr.want {
|
||||
t.Errorf("transferStatus got %v, want %v", g, tr.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *interruptedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
t.req = req
|
||||
if rng := req.Header.Get("Content-Range"); rng != "" && !strings.HasPrefix(rng, "bytes */") {
|
||||
t.interruptCount += 1
|
||||
if t.interruptCount%7 == 0 { // Respond with a "service unavailable" error
|
||||
res := &http.Response{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
Header: http.Header{},
|
||||
}
|
||||
t.rangeVal = fmt.Sprintf("bytes=0-%v", len(t.buf)-1) // Set the response for next time
|
||||
return res, nil
|
||||
}
|
||||
m := contentRangeRE.FindStringSubmatch(rng)
|
||||
if len(m) != 4 {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
start, err := strconv.ParseInt(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
end, err := strconv.ParseInt(m[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
totalSize, err := strconv.ParseInt(m[3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse content range: %v", rng)
|
||||
}
|
||||
partialSize := end - start + 1
|
||||
buf, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil || int64(len(buf)) != partialSize {
|
||||
return nil, fmt.Errorf("unable to read %v bytes from request data, n=%v: %v", partialSize, len(buf), err)
|
||||
}
|
||||
t.buf = append(t.buf, buf...)
|
||||
if totalSize == end+1 {
|
||||
t.statusCode = 200 // signify completion of transfer
|
||||
}
|
||||
}
|
||||
f := ioutil.NopCloser(unexpectedReader{})
|
||||
res := &http.Response{
|
||||
Body: f,
|
||||
StatusCode: t.statusCode,
|
||||
Header: http.Header{},
|
||||
}
|
||||
if t.rangeVal != "" {
|
||||
res.Header.Set("Range", t.rangeVal)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type interruptedTransport struct {
|
||||
req *http.Request
|
||||
statusCode int
|
||||
rangeVal string
|
||||
interruptCount int
|
||||
buf []byte
|
||||
progressBuf string
|
||||
}
|
||||
|
||||
func (tr *interruptedTransport) ProgressUpdate(current, total int64) {
|
||||
tr.progressBuf += fmt.Sprintf("%v, %v\n", current, total)
|
||||
}
|
||||
|
||||
func TestInterruptedTransferChunks(t *testing.T) {
|
||||
f, err := os.Open("googleapi.go")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to open googleapi.go: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
slurp, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to slurp file: %v", err)
|
||||
}
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to stat googleapi.go: %v", err)
|
||||
}
|
||||
tr := &interruptedTransport{
|
||||
statusCode: 308,
|
||||
buf: make([]byte, 0, st.Size()),
|
||||
}
|
||||
oldChunkSize := chunkSize
|
||||
defer func() { chunkSize = oldChunkSize }()
|
||||
chunkSize = 100 // override to process small chunks for test.
|
||||
|
||||
sleep = func(time.Duration) {} // override time.Sleep
|
||||
rx := &ResumableUpload{
|
||||
Client: &http.Client{Transport: tr},
|
||||
Media: f,
|
||||
MediaType: "text/plain",
|
||||
ContentLength: st.Size(),
|
||||
Callback: tr.ProgressUpdate,
|
||||
}
|
||||
res, err := rx.Upload(context.Background())
|
||||
if err != nil || res == nil || res.StatusCode != http.StatusOK {
|
||||
if res == nil {
|
||||
t.Errorf("transferChunks not successful, res=nil: %v", err)
|
||||
} else {
|
||||
t.Errorf("transferChunks not successful, statusCode=%v: %v", res.StatusCode, err)
|
||||
}
|
||||
}
|
||||
if len(tr.buf) != len(slurp) || bytes.Compare(tr.buf, slurp) != 0 {
|
||||
t.Errorf("transfered file corrupted:\ngot %s\nwant %s", tr.buf, slurp)
|
||||
}
|
||||
w := ""
|
||||
for i := chunkSize; i <= st.Size(); i += chunkSize {
|
||||
w += fmt.Sprintf("%v, %v\n", i, st.Size())
|
||||
}
|
||||
if st.Size()%chunkSize != 0 {
|
||||
w += fmt.Sprintf("%v, %v\n", st.Size(), st.Size())
|
||||
}
|
||||
if tr.progressBuf != w {
|
||||
t.Errorf("progress update error, got %v, want %v", tr.progressBuf, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelUpload(t *testing.T) {
|
||||
f, err := os.Open("googleapi.go")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to open googleapi.go: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to stat googleapi.go: %v", err)
|
||||
}
|
||||
tr := &interruptedTransport{
|
||||
statusCode: 308,
|
||||
buf: make([]byte, 0, st.Size()),
|
||||
}
|
||||
oldChunkSize := chunkSize
|
||||
defer func() { chunkSize = oldChunkSize }()
|
||||
chunkSize = 100 // override to process small chunks for test.
|
||||
|
||||
sleep = func(time.Duration) {} // override time.Sleep
|
||||
rx := &ResumableUpload{
|
||||
Client: &http.Client{Transport: tr},
|
||||
Media: f,
|
||||
MediaType: "text/plain",
|
||||
ContentLength: st.Size(),
|
||||
Callback: tr.ProgressUpdate,
|
||||
}
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
cancelFunc() // stop the upload that hasn't started yet
|
||||
res, err := rx.Upload(ctx)
|
||||
if err == nil || res == nil || res.StatusCode != http.StatusRequestTimeout {
|
||||
if res == nil {
|
||||
t.Errorf("transferChunks not successful, got res=nil, err=%v, want StatusRequestTimeout", err)
|
||||
} else {
|
||||
t.Errorf("transferChunks not successful, got statusCode=%v, err=%v, want StatusRequestTimeout", res.StatusCode, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +196,11 @@ var sharedCreateFlags = []cli.Flag{
|
||||
Name: "engine-storage-driver",
|
||||
Usage: "Specify a storage driver to use with the engine",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-env",
|
||||
Usage: "Specify environment variables to set in the engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "swarm",
|
||||
Usage: "Configure Machine with Swarm",
|
||||
@@ -379,6 +384,12 @@ var Commands = []cli.Command{
|
||||
Description: "Argument(s) are one or more machine names.",
|
||||
Action: cmdStart,
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "Get the status of a machine",
|
||||
Description: "Argument is a machine name.",
|
||||
Action: cmdStatus,
|
||||
},
|
||||
{
|
||||
Name: "stop",
|
||||
Usage: "Stop a machine",
|
||||
|
||||
@@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/docker/machine/log"
|
||||
|
||||
@@ -35,6 +36,10 @@ func cmdCreate(c *cli.Context) {
|
||||
log.Fatal("You must specify a machine name")
|
||||
}
|
||||
|
||||
if err := validateSwarmDiscovery(c.String("swarm-discovery")); err != nil {
|
||||
log.Fatalf("Error parsing swarm discovery: %s", err)
|
||||
}
|
||||
|
||||
certInfo := getCertPathInfo(c)
|
||||
|
||||
if err := setupCertificates(
|
||||
@@ -70,6 +75,7 @@ func cmdCreate(c *cli.Context) {
|
||||
},
|
||||
EngineOptions: &engine.EngineOptions{
|
||||
ArbitraryFlags: c.StringSlice("engine-opt"),
|
||||
Env: c.StringSlice("engine-env"),
|
||||
InsecureRegistry: c.StringSlice("engine-insecure-registry"),
|
||||
Labels: c.StringSlice("engine-label"),
|
||||
RegistryMirror: c.StringSlice("engine-registry-mirror"),
|
||||
@@ -117,3 +123,20 @@ func trimDriverFlags(driver string, cmds []cli.Command) ([]cli.Command, error) {
|
||||
|
||||
return filteredCmds, nil
|
||||
}
|
||||
|
||||
func validateSwarmDiscovery(discovery string) error {
|
||||
if discovery == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
matched, err := regexp.MatchString(`[^:]*://.*`, discovery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if matched {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Swarm Discovery URL was in the wrong format: %s", discovery)
|
||||
}
|
||||
|
||||
@@ -1 +1,22 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateSwarmDiscoveryErrorsGivenInvalidURL(t *testing.T) {
|
||||
err := validateSwarmDiscovery("foo")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestValidateSwarmDiscoveryAcceptsEmptyString(t *testing.T) {
|
||||
err := validateSwarmDiscovery("")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateSwarmDiscoveryAcceptsValidFormat(t *testing.T) {
|
||||
err := validateSwarmDiscovery("token://deadbeefcafe")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
16
commands/status.go
Normal file
16
commands/status.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func cmdStatus(c *cli.Context) {
|
||||
host := getHost(c)
|
||||
currentState, err := host.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", host.Name, err)
|
||||
}
|
||||
log.Info(currentState)
|
||||
}
|
||||
1
commands/status_test.go
Normal file
1
commands/status_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package commands
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docs/base:hugo
|
||||
FROM docs/base:latest
|
||||
MAINTAINER Mary Anthony <mary@docker.com> (@moxiegirl)
|
||||
|
||||
# to get the git info for this repo
|
||||
|
||||
@@ -24,6 +24,8 @@ Options:
|
||||
- `--google-scopes`: The scopes for OAuth 2.0 to Access Google APIs. See [Google Compute Engine Doc](https://cloud.google.com/storage/docs/authentication).
|
||||
- `--google-disk-size`: The disk size of instance.
|
||||
- `--google-disk-type`: The disk type of instance.
|
||||
- `--google-address`: Instance's static external IP (name or IP).
|
||||
- `--google-preemptible`: Instance preemptibility.
|
||||
|
||||
The GCE driver will use the `ubuntu-1404-trusty-v20150316` instance type unless otherwise specified.
|
||||
|
||||
@@ -39,3 +41,5 @@ Environment variables and default values:
|
||||
| `--google-scopes` | `GOOGLE_SCOPES` | `devstorage.read_only,logging.write` |
|
||||
| `--google-disk-size` | `GOOGLE_DISK_SIZE` | `10` |
|
||||
| `--google-disk-type` | `GOOGLE_DISK_TYPE` | `pd-standard` |
|
||||
| `--google-address` | `GOOGLE_ADDRESS` | - |
|
||||
| `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - |
|
||||
|
||||
@@ -20,7 +20,7 @@ them, then configures the `docker` client to talk to them. A "machine" is the
|
||||
combination of a Docker host and a configured client.
|
||||
|
||||
Once you create one or more Docker hosts, Docker Machine supplies a number of commands for
|
||||
managing them. Using these commands you can
|
||||
managing them. Using these commands you can
|
||||
|
||||
- start, inspect, stop, and restart a host
|
||||
- upgrade the Docker client and daemon
|
||||
@@ -28,7 +28,7 @@ managing them. Using these commands you can
|
||||
|
||||
## Understand Docker Machine basic concepts
|
||||
|
||||
Docker Machine allows you to provision Docker on virtual machines that reside either on your local system or on a cloud provider. Docker Machine creates a host on a VM and you use the Docker Engine client as needed to build images and create containers on the host.
|
||||
Docker Machine allows you to provision Docker on virtual machines that reside either on your local system or on a cloud provider. Docker Machine creates a host on a VM and you use the Docker Engine client as needed to build images and create containers on the host.
|
||||
|
||||
To create a virtual machine, you supply Docker Machine with the name of the driver you want use. The driver represents the virtual environment. For example, on a local Linux, Mac, or Windows system the driver is typically Oracle Virtual Box. For cloud providers, Docker Machine supports drivers such as AWS, Microsoft Azure, Digital Ocean and many more. The Docker Machine reference includes a complete [list of the supported drivers](/drivers).
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ weight=3
|
||||
Docker Machine is supported on Windows, OS X, and Linux and is installable as
|
||||
one standalone binary. The links to the binaries for the various platforms and
|
||||
architectures are available at the [Github
|
||||
Release](https://github.com/docker/machine/releases/tag/v0.3.0) page.
|
||||
Release](https://github.com/docker/machine/releases/) page.
|
||||
|
||||
|
||||
### OS X and Linux
|
||||
@@ -24,7 +24,7 @@ To install on OS X or Linux, download the proper binary to somewhere in your
|
||||
most OS X machines these commands should suffice:
|
||||
|
||||
```
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.3.0/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.3.1/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine
|
||||
$ chmod +x /usr/local/bin/docker-machine
|
||||
```
|
||||
|
||||
@@ -34,7 +34,7 @@ Now you should be able to check the version with `docker-machine -v`:
|
||||
|
||||
```
|
||||
$ docker-machine -v
|
||||
machine version 0.3.0
|
||||
machine version 0.3.1
|
||||
```
|
||||
|
||||
In order to run Docker commands on your machines without having to use SSH, make
|
||||
@@ -66,12 +66,12 @@ $ curl -L https://get.docker.com/builds/Windows/x86_64/docker-latest.exe > /bin/
|
||||
Next, install the Docker Machine binary:
|
||||
|
||||
```
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.3.0/docker-machine_windows-amd64.exe > /bin/docker-machine
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.3.1/docker-machine_windows-amd64.exe > /bin/docker-machine
|
||||
```
|
||||
|
||||
Now running `docker-machine` should work.
|
||||
|
||||
```
|
||||
$ docker-machine -v
|
||||
machine version 0.3.0
|
||||
```
|
||||
machine version 0.3.1
|
||||
```
|
||||
|
||||
@@ -60,6 +60,7 @@ Options:
|
||||
--engine-registry-mirror [--engine-registry-mirror option --engine-registry-mirror option] Specify registry mirrors to use
|
||||
--engine-label [--engine-label option --engine-label option] Specify labels for the created engine
|
||||
--engine-storage-driver "aufs" Specify a storage driver to use with the engine
|
||||
--engine-env Specify environment variables to set in the engine
|
||||
--swarm Configure Machine with Swarm
|
||||
--swarm-master Configure Machine to be a Swarm master
|
||||
--swarm-discovery Discovery service to use with Swarm
|
||||
@@ -146,6 +147,17 @@ $ docker-machine create -d virtualbox \
|
||||
gdns
|
||||
```
|
||||
|
||||
Additionally, Docker Machine supports a flag, `--engine-env`, which can be used to
|
||||
specify arbitrary environment variables to be set within the engine with the syntax `--engine-env name=value`. For example, to specify that the engine should use `example.com` as the proxy server, you could run the following create command:
|
||||
|
||||
```
|
||||
$ docker-machine create -d virtualbox \
|
||||
--engine-env HTTP_PROXY=http://example.com:8080 \
|
||||
--engine-env HTTPS_PROXY=https://example.com:8080 \
|
||||
--engine-env NO_PROXY=example2.com \
|
||||
proxbox
|
||||
```
|
||||
|
||||
## Specifying Docker Swarm options for the created machine
|
||||
|
||||
In addition to being able to configure Docker Engine options as listed above,
|
||||
|
||||
@@ -27,6 +27,7 @@ parent="smn_machine_ref"
|
||||
* [scp](scp.md)
|
||||
* [ssh](ssh.md)
|
||||
* [start](start.md)
|
||||
* [status](status.md)
|
||||
* [stop](stop.md)
|
||||
* [upgrade](upgrade.md)
|
||||
* [url](url.md)
|
||||
* [url](url.md)
|
||||
|
||||
@@ -11,7 +11,7 @@ parent="smn_machine_subcmds"
|
||||
# restart
|
||||
|
||||
Restart a machine. Oftentimes this is equivalent to
|
||||
`docker-machine stop; machine start`.
|
||||
`docker-machine stop; docker-machine start`.
|
||||
|
||||
```
|
||||
$ docker-machine restart dev
|
||||
|
||||
18
docs/reference/status.md
Normal file
18
docs/reference/status.md
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--[metadata]>
|
||||
+++
|
||||
title = "status"
|
||||
description = "Get the status of a machine"
|
||||
keywords = ["machine, status, subcommand"]
|
||||
[menu.main]
|
||||
parent="smn_machine_subcmds"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# status
|
||||
|
||||
Get the status of a machine.
|
||||
|
||||
```
|
||||
$ docker-machine status dev
|
||||
Running
|
||||
```
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -19,6 +20,8 @@ type ComputeUtil struct {
|
||||
userName string
|
||||
project string
|
||||
diskTypeURL string
|
||||
address string
|
||||
preemptible bool
|
||||
service *raw.Service
|
||||
zoneURL string
|
||||
authTokenPath string
|
||||
@@ -51,6 +54,8 @@ func newComputeUtil(driver *Driver) (*ComputeUtil, error) {
|
||||
userName: driver.SSHUser,
|
||||
project: driver.Project,
|
||||
diskTypeURL: driver.DiskType,
|
||||
address: driver.Address,
|
||||
preemptible: driver.Preemptible,
|
||||
service: service,
|
||||
zoneURL: apiURL + driver.Project + "/zones/" + driver.Zone,
|
||||
globalURL: apiURL + driver.Project + "/global",
|
||||
@@ -84,6 +89,31 @@ func (c *ComputeUtil) deleteDisk() error {
|
||||
return c.waitForRegionalOp(op.Name)
|
||||
}
|
||||
|
||||
// staticAddress returns the external static IP address.
|
||||
func (c *ComputeUtil) staticAddress() (string, error) {
|
||||
// is the address a name?
|
||||
isName, err := regexp.MatchString("[a-z]([-a-z0-9]*[a-z0-9])?", c.address)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if (!isName) {
|
||||
return c.address, nil
|
||||
}
|
||||
|
||||
// resolve the address by name
|
||||
externalAddress, err := c.service.Addresses.Get(c.project, c.region(), c.address).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return externalAddress.Address, nil
|
||||
}
|
||||
|
||||
func (c *ComputeUtil) region() (string) {
|
||||
return c.zone[:len(c.zone)-2]
|
||||
}
|
||||
|
||||
func (c *ComputeUtil) firewallRule() (*raw.Firewall, error) {
|
||||
return c.service.Firewalls.Get(c.project, firewallRule).Do()
|
||||
}
|
||||
@@ -178,7 +208,20 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
|
||||
Scopes: strings.Split(d.Scopes, ","),
|
||||
},
|
||||
},
|
||||
Scheduling: &raw.Scheduling{
|
||||
Preemptible: c.preemptible,
|
||||
},
|
||||
}
|
||||
|
||||
if c.address != "" {
|
||||
staticAddress, err := c.staticAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.NetworkInterfaces[0].AccessConfigs[0].NatIP = staticAddress
|
||||
}
|
||||
|
||||
disk, err := c.disk()
|
||||
if disk == nil || err != nil {
|
||||
instance.Disks[0].InitializeParams = &raw.AttachedDiskInitializeParams{
|
||||
|
||||
@@ -16,6 +16,8 @@ type Driver struct {
|
||||
Zone string
|
||||
MachineType string
|
||||
DiskType string
|
||||
Address string
|
||||
Preemptible bool
|
||||
Scopes string
|
||||
DiskSize int
|
||||
AuthTokenPath string
|
||||
@@ -79,6 +81,16 @@ func GetCreateFlags() []cli.Flag {
|
||||
Value: "pd-standard",
|
||||
EnvVar: "GOOGLE_DISK_TYPE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "google-address",
|
||||
Usage: "GCE Instance External IP",
|
||||
EnvVar: "GOOGLE_ADDRESS",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "google-preemptible",
|
||||
Usage: "GCE Instance Preemptibility",
|
||||
EnvVar: "GOOGLE_PREEMPTIBLE",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +123,8 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||
d.MachineType = flags.String("google-machine-type")
|
||||
d.DiskSize = flags.Int("google-disk-size")
|
||||
d.DiskType = flags.String("google-disk-type")
|
||||
d.Address = flags.String("google-address")
|
||||
d.Preemptible = flags.Bool("google-preemptible")
|
||||
d.AuthTokenPath = flags.String("google-auth-token")
|
||||
d.Project = flags.String("google-project")
|
||||
d.Scopes = flags.String("google-scopes")
|
||||
|
||||
@@ -437,7 +437,7 @@ func (c *GenericClient) Authenticate(d *Driver) error {
|
||||
return err
|
||||
}
|
||||
|
||||
provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.VERSION))
|
||||
provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.Version))
|
||||
|
||||
if d.Insecure {
|
||||
// Configure custom TLS settings.
|
||||
|
||||
@@ -42,7 +42,7 @@ func (c *Client) Authenticate(d *openstack.Driver) error {
|
||||
return err
|
||||
}
|
||||
|
||||
provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.VERSION))
|
||||
provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.Version))
|
||||
|
||||
err = rackspace.Authenticate(provider, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,12 +2,17 @@ package drivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/machine/log"
|
||||
"github.com/docker/machine/ssh"
|
||||
"github.com/docker/machine/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrExitCode255 = "255"
|
||||
)
|
||||
|
||||
func GetSSHClientFromDriver(d Driver) (ssh.Client, error) {
|
||||
addr, err := d.GetSSHHostname()
|
||||
if err != nil {
|
||||
@@ -28,6 +33,10 @@ func GetSSHClientFromDriver(d Driver) (ssh.Client, error) {
|
||||
|
||||
}
|
||||
|
||||
func isErr255Exit(err error) bool {
|
||||
return strings.Contains(err.Error(), ErrExitCode255)
|
||||
}
|
||||
|
||||
func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
|
||||
client, err := GetSSHClientFromDriver(d)
|
||||
if err != nil {
|
||||
@@ -38,6 +47,12 @@ func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
|
||||
|
||||
output, err := client.Output(command)
|
||||
log.Debugf("SSH cmd err, output: %v: %s", err, output)
|
||||
if err != nil && !isErr255Exit(err) {
|
||||
log.Error("SSH cmd error!")
|
||||
log.Errorf("command: %s", command)
|
||||
log.Errorf("err : %v", err)
|
||||
log.Fatalf("output : %s", output)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ type EngineOptions struct {
|
||||
ArbitraryFlags []string
|
||||
Dns []string
|
||||
GraphDir string
|
||||
Env []string
|
||||
Ipv6 bool
|
||||
InsecureRegistry []string
|
||||
Labels []string
|
||||
|
||||
@@ -19,7 +19,11 @@ type Filestore struct {
|
||||
}
|
||||
|
||||
func NewFilestore(rootPath string, caCert string, privateKey string) *Filestore {
|
||||
return &Filestore{path: rootPath, caCertPath: caCert, privateKeyPath: privateKey}
|
||||
return &Filestore{
|
||||
path: rootPath,
|
||||
caCertPath: caCert,
|
||||
privateKeyPath: privateKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Filestore) loadHost(name string) (*Host, error) {
|
||||
@@ -30,7 +34,10 @@ func (s Filestore) loadHost(name string) (*Host, error) {
|
||||
}
|
||||
}
|
||||
|
||||
host := &Host{Name: name, StorePath: hostPath}
|
||||
host := &Host{
|
||||
Name: name,
|
||||
StorePath: hostPath,
|
||||
}
|
||||
if err := host.LoadConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,7 +91,7 @@ func (s Filestore) List() ([]*Host, error) {
|
||||
|
||||
for _, file := range dir {
|
||||
// don't load hidden dirs; used for configs
|
||||
if file.IsDir() && strings.Index(file.Name(), ".") != 0 {
|
||||
if file.IsDir() && !strings.HasPrefix(file.Name(), ".") {
|
||||
host, err := s.Get(file.Name())
|
||||
if err != nil {
|
||||
log.Errorf("error loading host %q: %s", file.Name(), err)
|
||||
@@ -119,6 +126,10 @@ func (s Filestore) GetActive() (*Host, error) {
|
||||
}
|
||||
|
||||
dockerHost := os.Getenv("DOCKER_HOST")
|
||||
if dockerHost == "" {
|
||||
return nil, errors.New("No active host found: DOCKER_HOST not set")
|
||||
}
|
||||
|
||||
hostListItems := GetHostListItems(hosts)
|
||||
|
||||
for _, item := range hostListItems {
|
||||
|
||||
@@ -228,7 +228,7 @@ func TestStoreGetSetActive(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if host.Name != host.Name {
|
||||
t.Fatalf("Active host is not 'test', got %s", host.Name)
|
||||
if host.Name != hostTestName {
|
||||
t.Fatalf("Active host is not '%s', got %s", hostTestName, host.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/docker/machine/drivers"
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
@@ -19,20 +20,23 @@ import (
|
||||
"github.com/docker/machine/ssh"
|
||||
"github.com/docker/machine/state"
|
||||
"github.com/docker/machine/utils"
|
||||
"github.com/docker/machine/version"
|
||||
)
|
||||
|
||||
var (
|
||||
validHostNameChars = `[a-zA-Z0-9\-\.]`
|
||||
validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`)
|
||||
errMachineMustBeRunningForUpgrade = errors.New("Error: machine must be running to upgrade.")
|
||||
stateTimeoutDuration = time.Second * 3
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
Name string `json:"-"`
|
||||
DriverName string
|
||||
Driver drivers.Driver
|
||||
StorePath string
|
||||
HostOptions *HostOptions
|
||||
ConfigVersion int
|
||||
Driver drivers.Driver
|
||||
DriverName string
|
||||
HostOptions *HostOptions
|
||||
Name string `json:"-"`
|
||||
StorePath string
|
||||
}
|
||||
|
||||
type HostOptions struct {
|
||||
@@ -45,14 +49,9 @@ type HostOptions struct {
|
||||
}
|
||||
|
||||
type HostMetadata struct {
|
||||
DriverName string
|
||||
HostOptions HostOptions
|
||||
StorePath string
|
||||
CaCertPath string
|
||||
PrivateKeyPath string
|
||||
ServerCertPath string
|
||||
ServerKeyPath string
|
||||
ClientCertPath string
|
||||
ConfigVersion int
|
||||
DriverName string
|
||||
HostOptions HostOptions
|
||||
}
|
||||
|
||||
type HostListItem struct {
|
||||
@@ -64,6 +63,14 @@ type HostListItem struct {
|
||||
SwarmOptions swarm.SwarmOptions
|
||||
}
|
||||
|
||||
type ErrSavingConfig struct {
|
||||
wrappedErr error
|
||||
}
|
||||
|
||||
func (e ErrSavingConfig) Error() string {
|
||||
return fmt.Sprintf("Error saving config: %s", e.wrappedErr)
|
||||
}
|
||||
|
||||
func NewHost(name, driverName string, hostOptions *HostOptions) (*Host, error) {
|
||||
authOptions := hostOptions.AuthOptions
|
||||
storePath := filepath.Join(utils.GetMachineDir(), name)
|
||||
@@ -72,11 +79,12 @@ func NewHost(name, driverName string, hostOptions *HostOptions) (*Host, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &Host{
|
||||
Name: name,
|
||||
DriverName: driverName,
|
||||
Driver: driver,
|
||||
StorePath: storePath,
|
||||
HostOptions: hostOptions,
|
||||
Name: name,
|
||||
ConfigVersion: version.ConfigVersion,
|
||||
DriverName: driverName,
|
||||
Driver: driver,
|
||||
StorePath: storePath,
|
||||
HostOptions: hostOptions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -89,6 +97,7 @@ func LoadHost(name string, StorePath string) (*Host, error) {
|
||||
if err := host.LoadConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
@@ -276,24 +285,28 @@ func (h *Host) LoadConfig() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// First pass: find the driver name and load the driver
|
||||
var hostMetadata HostMetadata
|
||||
if err := json.Unmarshal(data, &hostMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
// Remember the machine name and store path so we don't have to pass it
|
||||
// through each struct in the migration.
|
||||
name := h.Name
|
||||
storePath := h.StorePath
|
||||
|
||||
authOptions := hostMetadata.HostOptions.AuthOptions
|
||||
// If we end up performing a migration, we should save afterwards so we don't have to do it again on subsequent invocations.
|
||||
migrationPerformed := h.ConfigVersion != version.ConfigVersion
|
||||
|
||||
driver, err := drivers.NewDriver(hostMetadata.DriverName, h.Name, h.StorePath, authOptions.CaCertPath, authOptions.PrivateKeyPath)
|
||||
migratedHost, err := MigrateHost(h, data)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Error getting migrated host: %s", err)
|
||||
}
|
||||
|
||||
h.Driver = driver
|
||||
*h = *migratedHost
|
||||
|
||||
// Second pass: unmarshal driver config into correct driver
|
||||
if err := json.Unmarshal(data, &h); err != nil {
|
||||
return err
|
||||
h.Name = name
|
||||
h.StorePath = storePath
|
||||
|
||||
if migrationPerformed {
|
||||
if err := h.SaveConfig(); err != nil {
|
||||
return fmt.Errorf("Error saving config after migration was performed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -346,7 +359,7 @@ func WaitForSSH(h *Host) error {
|
||||
return drivers.WaitForSSH(h.Driver)
|
||||
}
|
||||
|
||||
func getHostState(host Host, hostListItemsChan chan<- HostListItem) {
|
||||
func attemptGetHostState(host Host, stateQueryChan chan<- HostListItem) {
|
||||
currentState, err := host.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", host.Name, err)
|
||||
@@ -363,7 +376,7 @@ func getHostState(host Host, hostListItemsChan chan<- HostListItem) {
|
||||
|
||||
dockerHost := os.Getenv("DOCKER_HOST")
|
||||
|
||||
hostListItemsChan <- HostListItem{
|
||||
stateQueryChan <- HostListItem{
|
||||
Name: host.Name,
|
||||
Active: dockerHost == url && currentState != state.Stopped,
|
||||
DriverName: host.Driver.DriverName(),
|
||||
@@ -373,6 +386,29 @@ func getHostState(host Host, hostListItemsChan chan<- HostListItem) {
|
||||
}
|
||||
}
|
||||
|
||||
func getHostState(host Host, hostListItemsChan chan<- HostListItem) {
|
||||
// This channel is used to communicate the properties we are querying
|
||||
// about the host in the case of a successful read.
|
||||
stateQueryChan := make(chan HostListItem)
|
||||
|
||||
go attemptGetHostState(host, stateQueryChan)
|
||||
|
||||
select {
|
||||
// If we get back useful information, great. Forward it straight to
|
||||
// the original parent channel.
|
||||
case hli := <-stateQueryChan:
|
||||
hostListItemsChan <- hli
|
||||
|
||||
// Otherwise, give up after a predetermined duration.
|
||||
case <-time.After(stateTimeoutDuration):
|
||||
hostListItemsChan <- HostListItem{
|
||||
Name: host.Name,
|
||||
DriverName: host.Driver.DriverName(),
|
||||
State: state.Timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetHostListItems(hostList []*Host) []HostListItem {
|
||||
hostListItems := []HostListItem{}
|
||||
hostListItemsChan := make(chan HostListItem)
|
||||
|
||||
34
libmachine/host_v0.go
Normal file
34
libmachine/host_v0.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package libmachine
|
||||
|
||||
import "github.com/docker/machine/drivers"
|
||||
|
||||
type HostV0 struct {
|
||||
Name string `json:"-"`
|
||||
Driver drivers.Driver
|
||||
DriverName string
|
||||
ConfigVersion int
|
||||
HostOptions *HostOptions
|
||||
|
||||
StorePath string
|
||||
CaCertPath string
|
||||
PrivateKeyPath string
|
||||
ServerCertPath string
|
||||
ServerKeyPath string
|
||||
ClientCertPath string
|
||||
SwarmHost string
|
||||
SwarmMaster bool
|
||||
SwarmDiscovery string
|
||||
ClientKeyPath string
|
||||
}
|
||||
|
||||
type HostMetadataV0 struct {
|
||||
HostOptions HostOptions
|
||||
DriverName string
|
||||
|
||||
StorePath string
|
||||
CaCertPath string
|
||||
PrivateKeyPath string
|
||||
ServerCertPath string
|
||||
ServerKeyPath string
|
||||
ClientCertPath string
|
||||
}
|
||||
65
libmachine/migrate.go
Normal file
65
libmachine/migrate.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package libmachine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/machine/drivers"
|
||||
"github.com/docker/machine/version"
|
||||
)
|
||||
|
||||
func getMigratedHostMetadata(data []byte) (*HostMetadata, error) {
|
||||
// HostMetadata is for a "first pass" so we can then load the driver
|
||||
var (
|
||||
hostMetadata *HostMetadataV0
|
||||
)
|
||||
|
||||
if err := json.Unmarshal(data, &hostMetadata); err != nil {
|
||||
return &HostMetadata{}, err
|
||||
}
|
||||
|
||||
migratedHostMetadata := MigrateHostMetadataV0ToHostMetadataV1(hostMetadata)
|
||||
|
||||
return migratedHostMetadata, nil
|
||||
}
|
||||
|
||||
func MigrateHost(h *Host, data []byte) (*Host, error) {
|
||||
migratedHostMetadata, err := getMigratedHostMetadata(data)
|
||||
if err != nil {
|
||||
return &Host{}, err
|
||||
}
|
||||
|
||||
authOptions := migratedHostMetadata.HostOptions.AuthOptions
|
||||
|
||||
driver, err := drivers.NewDriver(
|
||||
migratedHostMetadata.DriverName,
|
||||
h.Name,
|
||||
h.StorePath,
|
||||
authOptions.CaCertPath,
|
||||
authOptions.PrivateKeyPath,
|
||||
)
|
||||
if err != nil {
|
||||
return &Host{}, err
|
||||
}
|
||||
|
||||
for h.ConfigVersion = migratedHostMetadata.ConfigVersion; h.ConfigVersion < version.ConfigVersion; h.ConfigVersion++ {
|
||||
switch h.ConfigVersion {
|
||||
case 0:
|
||||
hostV0 := &HostV0{
|
||||
Driver: driver,
|
||||
}
|
||||
if err := json.Unmarshal(data, &hostV0); err != nil {
|
||||
return &Host{}, fmt.Errorf("Error unmarshalling host config version 0: %s", err)
|
||||
}
|
||||
h = MigrateHostV0ToHostV1(hostV0)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
h.Driver = driver
|
||||
if err := json.Unmarshal(data, &h); err != nil {
|
||||
return &Host{}, fmt.Errorf("Error unmarshalling most recent host version: %s", err)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
113
libmachine/migrate_v0_v1.go
Normal file
113
libmachine/migrate_v0_v1.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package libmachine
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/engine"
|
||||
"github.com/docker/machine/libmachine/swarm"
|
||||
"github.com/docker/machine/utils"
|
||||
)
|
||||
|
||||
// In the 0.0.1 => 0.0.2 transition, the JSON representation of
|
||||
// machines changed from a "flat" to a more "nested" structure
|
||||
// for various options and configuration settings. To preserve
|
||||
// compatibility with existing machines, these migration functions
|
||||
// have been introduced. They preserve backwards compat at the expense
|
||||
// of some duplicated information.
|
||||
|
||||
// validates host config and modifies if needed
|
||||
// this is used for configuration updates
|
||||
func MigrateHostV0ToHostV1(hostV0 *HostV0) *Host {
|
||||
host := &Host{}
|
||||
|
||||
certInfoV0 := getCertInfoFromHost(hostV0)
|
||||
|
||||
host.HostOptions = &HostOptions{}
|
||||
host.HostOptions.EngineOptions = &engine.EngineOptions{}
|
||||
host.HostOptions.SwarmOptions = &swarm.SwarmOptions{
|
||||
Address: "",
|
||||
Discovery: hostV0.SwarmDiscovery,
|
||||
Host: hostV0.SwarmHost,
|
||||
Master: hostV0.SwarmMaster,
|
||||
}
|
||||
host.HostOptions.AuthOptions = &auth.AuthOptions{
|
||||
StorePath: hostV0.StorePath,
|
||||
CaCertPath: certInfoV0.CaCertPath,
|
||||
CaCertRemotePath: "",
|
||||
ServerCertPath: certInfoV0.ServerCertPath,
|
||||
ServerKeyPath: certInfoV0.ServerKeyPath,
|
||||
ClientKeyPath: certInfoV0.ClientKeyPath,
|
||||
ServerCertRemotePath: "",
|
||||
ServerKeyRemotePath: "",
|
||||
PrivateKeyPath: certInfoV0.CaKeyPath,
|
||||
ClientCertPath: certInfoV0.ClientCertPath,
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// fills nested host metadata and modifies if needed
|
||||
// this is used for configuration updates
|
||||
func MigrateHostMetadataV0ToHostMetadataV1(m *HostMetadataV0) *HostMetadata {
|
||||
hostMetadata := &HostMetadata{}
|
||||
hostMetadata.DriverName = m.DriverName
|
||||
hostMetadata.HostOptions.EngineOptions = &engine.EngineOptions{}
|
||||
hostMetadata.HostOptions.AuthOptions = &auth.AuthOptions{
|
||||
StorePath: m.StorePath,
|
||||
CaCertPath: m.CaCertPath,
|
||||
CaCertRemotePath: "",
|
||||
ServerCertPath: m.ServerCertPath,
|
||||
ServerKeyPath: m.ServerKeyPath,
|
||||
ClientKeyPath: "",
|
||||
ServerCertRemotePath: "",
|
||||
ServerKeyRemotePath: "",
|
||||
PrivateKeyPath: m.PrivateKeyPath,
|
||||
ClientCertPath: m.ClientCertPath,
|
||||
}
|
||||
|
||||
return hostMetadata
|
||||
}
|
||||
|
||||
func getCertInfoFromHost(h *HostV0) CertPathInfo {
|
||||
// setup cert paths
|
||||
caCertPath := h.CaCertPath
|
||||
caKeyPath := h.PrivateKeyPath
|
||||
clientCertPath := h.ClientCertPath
|
||||
clientKeyPath := h.ClientKeyPath
|
||||
serverCertPath := h.ServerCertPath
|
||||
serverKeyPath := h.ServerKeyPath
|
||||
|
||||
if caCertPath == "" {
|
||||
caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem")
|
||||
}
|
||||
|
||||
if caKeyPath == "" {
|
||||
caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem")
|
||||
}
|
||||
|
||||
if clientCertPath == "" {
|
||||
clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem")
|
||||
}
|
||||
|
||||
if clientKeyPath == "" {
|
||||
clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem")
|
||||
}
|
||||
|
||||
if serverCertPath == "" {
|
||||
serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem")
|
||||
}
|
||||
|
||||
if serverKeyPath == "" {
|
||||
serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem")
|
||||
}
|
||||
|
||||
return CertPathInfo{
|
||||
CaCertPath: caCertPath,
|
||||
CaKeyPath: caKeyPath,
|
||||
ClientCertPath: clientCertPath,
|
||||
ClientKeyPath: clientKeyPath,
|
||||
ServerCertPath: serverCertPath,
|
||||
ServerKeyPath: serverKeyPath,
|
||||
}
|
||||
}
|
||||
113
libmachine/migrate_v0_v1_test.go
Normal file
113
libmachine/migrate_v0_v1_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package libmachine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/engine"
|
||||
"github.com/docker/machine/libmachine/swarm"
|
||||
)
|
||||
|
||||
func TestMigrateHostV0ToV1(t *testing.T) {
|
||||
os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration")
|
||||
originalHost := &HostV0{
|
||||
HostOptions: nil,
|
||||
SwarmDiscovery: "token://foobar",
|
||||
SwarmHost: "1.2.3.4:2376",
|
||||
SwarmMaster: true,
|
||||
CaCertPath: "",
|
||||
PrivateKeyPath: "",
|
||||
ClientCertPath: "",
|
||||
ClientKeyPath: "",
|
||||
ServerCertPath: "",
|
||||
ServerKeyPath: "",
|
||||
}
|
||||
hostOptions := &HostOptions{
|
||||
SwarmOptions: &swarm.SwarmOptions{
|
||||
Master: true,
|
||||
Discovery: "token://foobar",
|
||||
Host: "1.2.3.4:2376",
|
||||
},
|
||||
AuthOptions: &auth.AuthOptions{
|
||||
CaCertPath: "/tmp/migration/certs/ca.pem",
|
||||
PrivateKeyPath: "/tmp/migration/certs/ca-key.pem",
|
||||
ClientCertPath: "/tmp/migration/certs/cert.pem",
|
||||
ClientKeyPath: "/tmp/migration/certs/key.pem",
|
||||
ServerCertPath: "/tmp/migration/certs/server.pem",
|
||||
ServerKeyPath: "/tmp/migration/certs/server-key.pem",
|
||||
},
|
||||
EngineOptions: &engine.EngineOptions{},
|
||||
}
|
||||
|
||||
expectedHost := &Host{
|
||||
HostOptions: hostOptions,
|
||||
}
|
||||
|
||||
host := MigrateHostV0ToHostV1(originalHost)
|
||||
|
||||
if !reflect.DeepEqual(host, expectedHost) {
|
||||
t.Logf("\n%+v\n%+v", host, expectedHost)
|
||||
t.Logf("\n%+v\n%+v", host.HostOptions, expectedHost.HostOptions)
|
||||
t.Fatal("Expected these structs to be equal, they were different")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateHostMetadataV0ToV1(t *testing.T) {
|
||||
metadata := &HostMetadataV0{
|
||||
HostOptions: HostOptions{
|
||||
EngineOptions: nil,
|
||||
AuthOptions: nil,
|
||||
},
|
||||
StorePath: "/tmp/store",
|
||||
CaCertPath: "/tmp/store/certs/ca.pem",
|
||||
ServerCertPath: "/tmp/store/certs/server.pem",
|
||||
}
|
||||
expectedAuthOptions := &auth.AuthOptions{
|
||||
StorePath: "/tmp/store",
|
||||
CaCertPath: "/tmp/store/certs/ca.pem",
|
||||
ServerCertPath: "/tmp/store/certs/server.pem",
|
||||
}
|
||||
|
||||
expectedMetadata := &HostMetadata{
|
||||
HostOptions: HostOptions{
|
||||
EngineOptions: &engine.EngineOptions{},
|
||||
AuthOptions: expectedAuthOptions,
|
||||
},
|
||||
}
|
||||
|
||||
m := MigrateHostMetadataV0ToHostMetadataV1(metadata)
|
||||
|
||||
if !reflect.DeepEqual(m, expectedMetadata) {
|
||||
t.Logf("\n%+v\n%+v", m, expectedMetadata)
|
||||
t.Fatal("Expected these structs to be equal, they were different")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests a function which "prefills" certificate information for a host
|
||||
// due to a schema migration from "flat" to a "nested" structure.
|
||||
func TestGetCertInfoFromHost(t *testing.T) {
|
||||
os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration")
|
||||
host := &HostV0{
|
||||
CaCertPath: "",
|
||||
PrivateKeyPath: "",
|
||||
ClientCertPath: "",
|
||||
ClientKeyPath: "",
|
||||
ServerCertPath: "",
|
||||
ServerKeyPath: "",
|
||||
}
|
||||
expectedCertInfo := CertPathInfo{
|
||||
CaCertPath: "/tmp/migration/certs/ca.pem",
|
||||
CaKeyPath: "/tmp/migration/certs/ca-key.pem",
|
||||
ClientCertPath: "/tmp/migration/certs/cert.pem",
|
||||
ClientKeyPath: "/tmp/migration/certs/key.pem",
|
||||
ServerCertPath: "/tmp/migration/certs/server.pem",
|
||||
ServerKeyPath: "/tmp/migration/certs/server-key.pem",
|
||||
}
|
||||
certInfo := getCertInfoFromHost(host)
|
||||
if !reflect.DeepEqual(expectedCertInfo, certInfo) {
|
||||
t.Log("\n\n\n", expectedCertInfo, "\n\n\n", certInfo)
|
||||
t.Fatal("Expected these structs to be equal, they were different")
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,9 @@ DOCKER_STORAGE={{.EngineOptions.StorageDriver}}
|
||||
DOCKER_TLS=auto
|
||||
SERVERKEY={{.AuthOptions.ServerKeyRemotePath}}
|
||||
SERVERCERT={{.AuthOptions.ServerCertRemotePath}}
|
||||
|
||||
{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\"
|
||||
{{end}}
|
||||
`
|
||||
t, err := template.New("engineConfig").Parse(engineConfigTmpl)
|
||||
if err != nil {
|
||||
@@ -169,6 +172,10 @@ func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) {
|
||||
provisioner.OsReleaseInfo = info
|
||||
}
|
||||
|
||||
func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error) {
|
||||
return provisioner.OsReleaseInfo, nil
|
||||
}
|
||||
|
||||
func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error {
|
||||
provisioner.SwarmOptions = swarmOptions
|
||||
provisioner.AuthOptions = authOptions
|
||||
|
||||
@@ -4,13 +4,6 @@ import (
|
||||
"github.com/docker/machine/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: eventually the RPM install process will be integrated
|
||||
// into the get.docker.com install script; for now
|
||||
// we install via vendored RPMs
|
||||
dockerCentosRPMPath = "https://get.docker.com/rpm/1.7.0/centos-7/RPMS/x86_64/docker-engine-1.7.0-1.el7.centos.x86_64.rpm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("Centos", &RegisteredProvisioner{
|
||||
New: NewCentosProvisioner,
|
||||
@@ -28,7 +21,6 @@ func NewCentosProvisioner(d drivers.Driver) Provisioner {
|
||||
p := &CentosProvisioner{
|
||||
RedHatProvisioner{
|
||||
GenericProvisioner: g,
|
||||
DockerRPMPath: dockerCentosRPMPath,
|
||||
},
|
||||
}
|
||||
return p
|
||||
|
||||
28
libmachine/provision/centos_test.go
Normal file
28
libmachine/provision/centos_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCentosGenerateYumRepoList(t *testing.T) {
|
||||
info := &OsRelease{
|
||||
Id: "centos",
|
||||
}
|
||||
p := NewCentosProvisioner(nil)
|
||||
p.SetOsReleaseInfo(info)
|
||||
|
||||
buf, err := generateYumRepoList(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := regexp.MatchString(".*centos/7.*", buf.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !m {
|
||||
t.Fatalf("expected match for centos/7")
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ manage \
|
||||
--restart=always \
|
||||
--name swarm-agent \
|
||||
{{.SwarmImage}} \
|
||||
join --addr {{.Ip}}:{{.DockerPort}} {{.SwarmOptions.Discovery}}
|
||||
join --advertise {{.Ip}}:{{.DockerPort}} {{.SwarmOptions.Discovery}}
|
||||
`
|
||||
|
||||
if swarmOptions.Master {
|
||||
|
||||
@@ -172,6 +172,7 @@ MountFlags=slave
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -4,13 +4,6 @@ import (
|
||||
"github.com/docker/machine/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: eventually the RPM install process will be integrated
|
||||
// into the get.docker.com install script; for now
|
||||
// we install via vendored RPMs
|
||||
dockerFedoraRPMPath = "https://get.docker.com/rpm/1.7.0/fedora-21/RPMS/x86_64/docker-engine-1.7.0-1.fc21.x86_64.rpm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("Fedora", &RegisteredProvisioner{
|
||||
New: NewFedoraProvisioner,
|
||||
@@ -28,7 +21,6 @@ func NewFedoraProvisioner(d drivers.Driver) Provisioner {
|
||||
p := &FedoraProvisioner{
|
||||
RedHatProvisioner{
|
||||
GenericProvisioner: g,
|
||||
DockerRPMPath: dockerFedoraRPMPath,
|
||||
},
|
||||
}
|
||||
return p
|
||||
|
||||
28
libmachine/provision/fedora_test.go
Normal file
28
libmachine/provision/fedora_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFedoraGenerateYumRepoList(t *testing.T) {
|
||||
info := &OsRelease{
|
||||
Id: "fedora",
|
||||
}
|
||||
p := NewCentosProvisioner(nil)
|
||||
p.SetOsReleaseInfo(info)
|
||||
|
||||
buf, err := generateYumRepoList(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := regexp.MatchString(".*fedora/22.*", buf.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !m {
|
||||
t.Fatalf("expected match for fedora/22")
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,10 @@ func (provisioner *GenericProvisioner) SetOsReleaseInfo(info *OsRelease) {
|
||||
provisioner.OsReleaseInfo = info
|
||||
}
|
||||
|
||||
func (provisioner *GenericProvisioner) GetOsReleaseInfo() (*OsRelease, error) {
|
||||
return provisioner.OsReleaseInfo, nil
|
||||
}
|
||||
|
||||
func (provisioner *GenericProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) {
|
||||
var (
|
||||
engineCfg bytes.Buffer
|
||||
@@ -91,6 +95,8 @@ DOCKER_OPTS='
|
||||
{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}}
|
||||
{{ end }}
|
||||
'
|
||||
{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\"
|
||||
{{end}}
|
||||
`
|
||||
t, err := template.New("engineConfig").Parse(engineConfigTmpl)
|
||||
if err != nil {
|
||||
|
||||
@@ -69,7 +69,8 @@ func (osr *OsRelease) ParseOsRelease(osReleaseContents []byte) error {
|
||||
for scanner.Scan() {
|
||||
key, val, err := parseLine(scanner.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
log.Warn("Warning: got an invalid line error parsing /etc/os-release: %s", err)
|
||||
continue
|
||||
}
|
||||
if err := osr.setIfPossible(key, val); err != nil {
|
||||
log.Debug(err)
|
||||
|
||||
@@ -56,6 +56,9 @@ type Provisioner interface {
|
||||
// Set the OS Release info depending on how it's represented
|
||||
// internally
|
||||
SetOsReleaseInfo(info *OsRelease)
|
||||
|
||||
// Get the OS Release info for the current provisioner
|
||||
GetOsReleaseInfo() (*OsRelease, error)
|
||||
}
|
||||
|
||||
// Detection
|
||||
|
||||
@@ -2,6 +2,7 @@ package provision
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
@@ -15,13 +16,31 @@ import (
|
||||
"github.com/docker/machine/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: eventually the RPM install process will be integrated
|
||||
// into the get.docker.com install script; for now
|
||||
// we install via vendored RPMs
|
||||
dockerRHELRPMPath = "https://get.docker.com/rpm/1.7.0/centos-7/RPMS/x86_64/docker-engine-1.7.0-1.el7.centos.x86_64.rpm"
|
||||
var (
|
||||
ErrUnknownYumOsRelease = errors.New("unknown OS for Yum repository")
|
||||
|
||||
packageListTemplate = `[docker]
|
||||
name=Docker Stable Repository
|
||||
baseurl=https://yum.dockerproject.org/repo/main/{{.OsRelease}}/{{.OsReleaseVersion}}
|
||||
priority=1
|
||||
enabled=1
|
||||
gpgkey=https://yum.dockerproject.org/gpg
|
||||
`
|
||||
engineConfigTemplate = `[Service]
|
||||
ExecStart=/usr/bin/docker -d -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }}
|
||||
MountFlags=slave
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}}
|
||||
`
|
||||
)
|
||||
|
||||
type PackageListInfo struct {
|
||||
OsRelease string
|
||||
OsReleaseVersion string
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("RedHat", &RegisteredProvisioner{
|
||||
New: NewRedHatProvisioner,
|
||||
@@ -39,13 +58,11 @@ func NewRedHatProvisioner(d drivers.Driver) Provisioner {
|
||||
},
|
||||
Driver: d,
|
||||
},
|
||||
DockerRPMPath: dockerRHELRPMPath,
|
||||
}
|
||||
}
|
||||
|
||||
type RedHatProvisioner struct {
|
||||
GenericProvisioner
|
||||
DockerRPMPath string
|
||||
}
|
||||
|
||||
func (provisioner *RedHatProvisioner) SSHCommand(args string) (string, error) {
|
||||
@@ -155,7 +172,11 @@ func installDocker(provisioner *RedHatProvisioner) error {
|
||||
func (provisioner *RedHatProvisioner) installOfficialDocker() error {
|
||||
log.Debug("installing docker")
|
||||
|
||||
if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo yum install -y --nogpgcheck %s", provisioner.DockerRPMPath)); err != nil {
|
||||
if err := provisioner.ConfigurePackageList(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := provisioner.SSHCommand("sudo yum install -y docker-engine"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -230,24 +251,12 @@ func (provisioner *RedHatProvisioner) GenerateDockerOptions(dockerPort int) (*Do
|
||||
configPath = provisioner.DaemonOptionsFile
|
||||
)
|
||||
|
||||
// remove existing
|
||||
//if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo rm %s", configPath)); err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName())
|
||||
provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel)
|
||||
|
||||
// systemd / redhat will not load options if they are on newlines
|
||||
// instead, it just continues with a different set of options; yeah...
|
||||
engineConfigTmpl := `[Service]
|
||||
ExecStart=/usr/bin/docker -d -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }}
|
||||
MountFlags=slave
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
`
|
||||
t, err := template.New("engineConfig").Parse(engineConfigTmpl)
|
||||
t, err := template.New("engineConfig").Parse(engineConfigTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -267,3 +276,53 @@ LimitCORE=infinity
|
||||
EngineOptionsPath: daemonOptsDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateYumRepoList(provisioner Provisioner) (*bytes.Buffer, error) {
|
||||
packageListInfo := &PackageListInfo{}
|
||||
|
||||
releaseInfo, err := provisioner.GetOsReleaseInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch releaseInfo.Id {
|
||||
case "rhel", "centos":
|
||||
// rhel and centos both use the "centos" repo
|
||||
packageListInfo.OsRelease = "centos"
|
||||
packageListInfo.OsReleaseVersion = "7"
|
||||
case "fedora":
|
||||
packageListInfo.OsRelease = "fedora"
|
||||
packageListInfo.OsReleaseVersion = "22"
|
||||
default:
|
||||
return nil, ErrUnknownYumOsRelease
|
||||
}
|
||||
|
||||
t, err := template.New("packageList").Parse(packageListTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := t.Execute(&buf, packageListInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &buf, nil
|
||||
}
|
||||
|
||||
func (provisioner *RedHatProvisioner) ConfigurePackageList() error {
|
||||
buf, err := generateYumRepoList(provisioner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we cannot use %q here as it combines the newlines in the formatting
|
||||
// on transport causing yum to not use the repo
|
||||
packageCmd := fmt.Sprintf("echo \"%s\" | sudo tee /etc/yum.repos.d/docker.repo", buf.String())
|
||||
if _, err := provisioner.SSHCommand(packageCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
28
libmachine/provision/redhat_test.go
Normal file
28
libmachine/provision/redhat_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRedHatGenerateYumRepoList(t *testing.T) {
|
||||
info := &OsRelease{
|
||||
Id: "rhel",
|
||||
}
|
||||
p := NewRedHatProvisioner(nil)
|
||||
p.SetOsReleaseInfo(info)
|
||||
|
||||
buf, err := generateYumRepoList(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := regexp.MatchString(".*centos/7.*", buf.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !m {
|
||||
t.Fatalf("expected match for centos/7")
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func ConfigureAuth(p Provisioner) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = p.SSHCommand(fmt.Sprintf("printf \"%s\" | sudo tee %s", dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil {
|
||||
if _, err = p.SSHCommand(fmt.Sprintf("printf %%s \"%s\" | sudo tee %s", dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -67,7 +67,7 @@ func main() {
|
||||
app.Commands = commands.Commands
|
||||
app.CommandNotFound = cmdNotFound
|
||||
app.Usage = "Create and manage machines running Docker."
|
||||
app.Version = version.VERSION + " (" + version.GITCOMMIT + ")"
|
||||
app.Version = version.Version + " (" + version.GitCommit + ")"
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
|
||||
@@ -21,4 +21,4 @@ fi
|
||||
# Get rid of existing binaries
|
||||
rm -f docker-machine*
|
||||
rm -rf Godeps/_workspace/pkg
|
||||
docker run --rm -v `pwd`:/go/src/github.com/docker/machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="docker-machine_{{.OS}}-{{.Arch}}" -ldflags="-w -X github.com/docker/machine/version.GITCOMMIT `git rev-parse --short HEAD`"
|
||||
docker run --rm -v `pwd`:/go/src/github.com/docker/machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="docker-machine_{{.OS}}-{{.Arch}}" -ldflags="-w -X github.com/docker/machine/version.GitCommit `git rev-parse --short HEAD`"
|
||||
|
||||
@@ -12,6 +12,7 @@ const (
|
||||
Stopping
|
||||
Starting
|
||||
Error
|
||||
Timeout
|
||||
)
|
||||
|
||||
var states = []string{
|
||||
@@ -23,6 +24,7 @@ var states = []string{
|
||||
"Stopping",
|
||||
"Starting",
|
||||
"Error",
|
||||
"Timeout",
|
||||
}
|
||||
|
||||
// Given a State type, returns its string representation
|
||||
|
||||
19
test/integration/core/arbitrary-engine-envs.bats
Normal file
19
test/integration/core/arbitrary-engine-envs.bats
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load ${BASE_TEST_DIR}/helpers.bash
|
||||
|
||||
|
||||
@test "$DRIVER: create with arbitrary engine envs" {
|
||||
|
||||
run machine create -d $DRIVER \
|
||||
--engine-env=TEST=VALUE \
|
||||
$NAME
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "$DRIVER: test docker process envs" {
|
||||
|
||||
# get pid of docker process, check process envs for set Environment Variable from above test
|
||||
run machine ssh $NAME 'pgrep -f "docker -d" | xargs -I % sudo cat /proc/%/environ | grep -q "TEST=VALUE"'
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
@@ -104,3 +104,9 @@ load ${BASE_TEST_DIR}/helpers.bash
|
||||
[ "$status" -eq 0 ]
|
||||
[[ ${lines[1]} == *"Running"* ]]
|
||||
}
|
||||
|
||||
@test "$DRIVER: status" {
|
||||
run machine status $NAME
|
||||
[ "$status" -eq 0 ]
|
||||
[[ ${output} == *"Running"* ]]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package version
|
||||
|
||||
var (
|
||||
// VERSION should be updated by hand at each release
|
||||
VERSION = "0.4.0-dev"
|
||||
// ConfigVersion dictates which version of the config.json format is
|
||||
// used. It needs to be bumped if there is a breaking change, and
|
||||
// therefore migration, introduced to the config file format.
|
||||
ConfigVersion = 1
|
||||
|
||||
// GITCOMMIT will be overwritten automatically by the build system
|
||||
GITCOMMIT = "HEAD"
|
||||
// Version should be updated by hand at each release
|
||||
Version = "0.4.0-dev"
|
||||
|
||||
// GitCommit will be overwritten automatically by the build system
|
||||
GitCommit = "HEAD"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user