diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a2bbefe1..933309ce 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -8,14 +8,17 @@ "Deps": [ { "ImportPath": "cloud.google.com/go/compute/metadata", + "Comment": "v0.1.0-115-g3b1ae45", "Rev": "3b1ae45394a234c385be014e9a488f2bb6eef821" }, { "ImportPath": "cloud.google.com/go/internal", + "Comment": "v0.1.0-115-g3b1ae45", "Rev": "3b1ae45394a234c385be014e9a488f2bb6eef821" }, { "ImportPath": "github.com/PuerkitoBio/purell", + "Comment": "v1.0.0", "Rev": "8a290539e2e8629dbc4e6bad948158f790ec31f4" }, { @@ -44,14 +47,17 @@ }, { "ImportPath": "github.com/coreos/pkg/health", + "Comment": "v2-8-gfa29b1d", "Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" }, { "ImportPath": "github.com/coreos/pkg/httputil", + "Comment": "v2-8-gfa29b1d", "Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" }, { "ImportPath": "github.com/coreos/pkg/timeutil", + "Comment": "v2-8-gfa29b1d", "Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" }, { @@ -60,10 +66,12 @@ }, { "ImportPath": "github.com/docker/distribution/digest", + "Comment": "v2.4.0-rc.1-38-gcd27f17", "Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51" }, { "ImportPath": "github.com/docker/distribution/reference", + "Comment": "v2.4.0-rc.1-38-gcd27f17", "Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51" }, { @@ -76,14 +84,17 @@ }, { "ImportPath": "github.com/emicklei/go-restful", + "Comment": "2.2.0-4-gff4f55a", "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" }, { "ImportPath": "github.com/emicklei/go-restful-swagger12", + "Comment": "1.0.1", "Rev": "dcef7f55730566d41eae5db10e7d6981829720f6" }, { "ImportPath": "github.com/emicklei/go-restful/log", + "Comment": "2.2.0-4-gff4f55a", "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" }, { @@ -116,10 +127,12 @@ }, { "ImportPath": "github.com/gogo/protobuf/proto", + "Comment": "v0.4-3-gc0656ed", "Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7" }, { "ImportPath": "github.com/gogo/protobuf/sortkeys", + "Comment": "v0.4-3-gc0656ed", "Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7" }, { @@ -152,6 +165,7 @@ }, { "ImportPath": "github.com/imdario/mergo", + "Comment": "0.1.3-8-g6633656", "Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc" }, { @@ -184,6 +198,7 @@ }, { "ImportPath": "github.com/stretchr/testify/assert", + "Comment": "v1.0-88-ge3a8ff8", "Rev": "e3a8ff8ce36581f87a15341206f205b1da467059" }, { @@ -284,6 +299,7 @@ }, { "ImportPath": "gopkg.in/inf.v0", + "Comment": "v0.9.0", "Rev": "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4" }, { @@ -292,211 +308,211 @@ }, { "ImportPath": "k8s.io/apimachinery/pkg/api/equality", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/errors", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/meta", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/resource", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/testing", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apimachinery", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/announced", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/labels", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/openapi", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/schema", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/selection", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/types", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/cache", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/clock", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/diff", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/errors", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/framer", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/json", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/net", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/rand", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/runtime", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/sets", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation/field", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/wait", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/yaml", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/version", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/pkg/watch", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "Rev": "cff8db64dd5b6fb0166dc2cf36e39c7ff4fe48c8" } ] } diff --git a/vendor/github.com/howeyc/gopass/pass.go b/vendor/github.com/howeyc/gopass/pass.go index 31c853ae..f5bd5a51 100644 --- a/vendor/github.com/howeyc/gopass/pass.go +++ b/vendor/github.com/howeyc/gopass/pass.go @@ -7,9 +7,14 @@ import ( "os" ) -var defaultGetCh = func() (byte, error) { +type FdReader interface { + io.Reader + Fd() uintptr +} + +var defaultGetCh = func(r io.Reader) (byte, error) { buf := make([]byte, 1) - if n, err := os.Stdin.Read(buf); n == 0 || err != nil { + if n, err := r.Read(buf); n == 0 || err != nil { if err != nil { return 0, err } @@ -28,9 +33,10 @@ var ( ) // getPasswd returns the input read from terminal. +// If prompt is not empty, it will be output as a prompt to the user // If masked is true, typing will be matched by asterisks on the screen. // Otherwise, typing will echo nothing. -func getPasswd(masked bool) ([]byte, error) { +func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) { var err error var pass, bs, mask []byte if masked { @@ -38,26 +44,33 @@ func getPasswd(masked bool) ([]byte, error) { mask = []byte("*") } - if isTerminal(os.Stdin.Fd()) { - if oldState, err := makeRaw(os.Stdin.Fd()); err != nil { + if isTerminal(r.Fd()) { + if oldState, err := makeRaw(r.Fd()); err != nil { return pass, err } else { - defer restore(os.Stdin.Fd(), oldState) + defer func() { + restore(r.Fd(), oldState) + fmt.Fprintln(w) + }() } } + if prompt != "" { + fmt.Fprint(w, prompt) + } + // Track total bytes read, not just bytes in the password. This ensures any // errors that might flood the console with nil or -1 bytes infinitely are // capped. var counter int for counter = 0; counter <= maxLength; counter++ { - if v, e := getch(); e != nil { + if v, e := getch(r); e != nil { err = e break } else if v == 127 || v == 8 { if l := len(pass); l > 0 { pass = pass[:l-1] - fmt.Print(string(bs)) + fmt.Fprint(w, string(bs)) } } else if v == 13 || v == 10 { break @@ -66,7 +79,7 @@ func getPasswd(masked bool) ([]byte, error) { break } else if v != 0 { pass = append(pass, v) - fmt.Print(string(mask)) + fmt.Fprint(w, string(mask)) } } @@ -74,18 +87,24 @@ func getPasswd(masked bool) ([]byte, error) { err = ErrMaxLengthExceeded } - fmt.Println() return pass, err } // GetPasswd returns the password read from the terminal without echoing input. // The returned byte array does not include end-of-line characters. func GetPasswd() ([]byte, error) { - return getPasswd(false) + return getPasswd("", false, os.Stdin, os.Stdout) } // GetPasswdMasked returns the password read from the terminal, echoing asterisks. // The returned byte array does not include end-of-line characters. func GetPasswdMasked() ([]byte, error) { - return getPasswd(true) + return getPasswd("", true, os.Stdin, os.Stdout) +} + +// GetPasswdPrompt prompts the user and returns the password read from the terminal. +// If mask is true, then asterisks are echoed. +// The returned byte array does not include end-of-line characters. +func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) { + return getPasswd(prompt, mask, r, w) } diff --git a/vendor/github.com/juju/ratelimit/ratelimit.go b/vendor/github.com/juju/ratelimit/ratelimit.go index 3ef32fbc..1c3f25b2 100644 --- a/vendor/github.com/juju/ratelimit/ratelimit.go +++ b/vendor/github.com/juju/ratelimit/ratelimit.go @@ -2,7 +2,7 @@ // Licensed under the LGPLv3 with static-linking exception. // See LICENCE file for details. -// The ratelimit package provides an efficient token bucket implementation +// Package ratelimit provides an efficient token bucket implementation // that can be used to limit the rate of arbitrary things. // See http://en.wikipedia.org/wiki/Token_bucket. package ratelimit @@ -21,6 +21,7 @@ type Bucket struct { capacity int64 quantum int64 fillInterval time.Duration + clock Clock // The mutex guards the fields following it. mu sync.Mutex @@ -33,12 +34,37 @@ type Bucket struct { availTick int64 } +// Clock is used to inject testable fakes. +type Clock interface { + Now() time.Time + Sleep(d time.Duration) +} + +// realClock implements Clock in terms of standard time functions. +type realClock struct{} + +// Now is identical to time.Now. +func (realClock) Now() time.Time { + return time.Now() +} + +// Sleep is identical to time.Sleep. +func (realClock) Sleep(d time.Duration) { + time.Sleep(d) +} + // NewBucket returns a new token bucket that fills at the // rate of one token every fillInterval, up to the given // maximum capacity. Both arguments must be // positive. The bucket is initially full. func NewBucket(fillInterval time.Duration, capacity int64) *Bucket { - return NewBucketWithQuantum(fillInterval, capacity, 1) + return NewBucketWithClock(fillInterval, capacity, realClock{}) +} + +// NewBucketWithClock is identical to NewBucket but injects a testable clock +// interface. +func NewBucketWithClock(fillInterval time.Duration, capacity int64, clock Clock) *Bucket { + return NewBucketWithQuantumAndClock(fillInterval, capacity, 1, clock) } // rateMargin specifes the allowed variance of actual @@ -51,12 +77,18 @@ const rateMargin = 0.01 // at high rates, the actual rate may be up to 1% different from the // specified rate. func NewBucketWithRate(rate float64, capacity int64) *Bucket { + return NewBucketWithRateAndClock(rate, capacity, realClock{}) +} + +// NewBucketWithRateAndClock is identical to NewBucketWithRate but injects a +// testable clock interface. +func NewBucketWithRateAndClock(rate float64, capacity int64, clock Clock) *Bucket { for quantum := int64(1); quantum < 1<<50; quantum = nextQuantum(quantum) { fillInterval := time.Duration(1e9 * float64(quantum) / rate) if fillInterval <= 0 { continue } - tb := NewBucketWithQuantum(fillInterval, capacity, quantum) + tb := NewBucketWithQuantumAndClock(fillInterval, capacity, quantum, clock) if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin { return tb } @@ -79,6 +111,12 @@ func nextQuantum(q int64) int64 { // the specification of the quantum size - quantum tokens // are added every fillInterval. func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket { + return NewBucketWithQuantumAndClock(fillInterval, capacity, quantum, realClock{}) +} + +// NewBucketWithQuantumAndClock is identical to NewBucketWithQuantum but injects +// a testable clock interface. +func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket { if fillInterval <= 0 { panic("token bucket fill interval is not > 0") } @@ -89,7 +127,8 @@ func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) * panic("token bucket quantum is not > 0") } return &Bucket{ - startTime: time.Now(), + clock: clock, + startTime: clock.Now(), capacity: capacity, quantum: quantum, avail: capacity, @@ -101,7 +140,7 @@ func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) * // available. func (tb *Bucket) Wait(count int64) { if d := tb.Take(count); d > 0 { - time.Sleep(d) + tb.clock.Sleep(d) } } @@ -113,7 +152,7 @@ func (tb *Bucket) Wait(count int64) { func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool { d, ok := tb.TakeMaxDuration(count, maxWait) if d > 0 { - time.Sleep(d) + tb.clock.Sleep(d) } return ok } @@ -127,7 +166,7 @@ const infinityDuration time.Duration = 0x7fffffffffffffff // Note that if the request is irrevocable - there is no way to return // tokens to the bucket once this method commits us to taking them. func (tb *Bucket) Take(count int64) time.Duration { - d, _ := tb.take(time.Now(), count, infinityDuration) + d, _ := tb.take(tb.clock.Now(), count, infinityDuration) return d } @@ -141,14 +180,14 @@ func (tb *Bucket) Take(count int64) time.Duration { // wait until the tokens are actually available, and reports // true. func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool) { - return tb.take(time.Now(), count, maxWait) + return tb.take(tb.clock.Now(), count, maxWait) } // TakeAvailable takes up to count immediately available tokens from the // bucket. It returns the number of tokens removed, or zero if there are // no available tokens. It does not block. func (tb *Bucket) TakeAvailable(count int64) int64 { - return tb.takeAvailable(time.Now(), count) + return tb.takeAvailable(tb.clock.Now(), count) } // takeAvailable is the internal version of TakeAvailable - it takes the @@ -178,7 +217,7 @@ func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 { // tokens could have changed in the meantime. This method is intended // primarily for metrics reporting and debugging. func (tb *Bucket) Available() int64 { - return tb.available(time.Now()) + return tb.available(tb.clock.Now()) } // available is the internal version of available - it takes the current time as