mirror of https://github.com/docker/docs.git
Use uuid library from distribution
Distribution updated context library to use its own uuid library which does not panic on entropy exhaustion. Updated to use latest context library from distribution. Updated auth to match context and uuid library version. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
3f5a78d235
commit
c87caf3979
|
@ -5,11 +5,6 @@
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
|
||||||
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
|
||||||
"Comment": "null-15",
|
|
||||||
"Rev": "35bc42037350f0078e3c974c6ea690f1926603ab"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/BurntSushi/toml",
|
"ImportPath": "github.com/BurntSushi/toml",
|
||||||
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
||||||
|
@ -29,13 +24,18 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/distribution/context",
|
"ImportPath": "github.com/docker/distribution/context",
|
||||||
"Comment": "v2.0.0-rc.3-2-g75919b7",
|
"Comment": "v2.0.0-332-g79a4ca2",
|
||||||
"Rev": "75919b7dcc5d53894d7e8f1584e91ae148335f3a"
|
"Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/distribution/registry/auth",
|
"ImportPath": "github.com/docker/distribution/registry/auth",
|
||||||
"Comment": "v2.0.0-rc.3-2-g75919b7",
|
"Comment": "v2.0.0-332-g79a4ca2",
|
||||||
"Rev": "75919b7dcc5d53894d7e8f1584e91ae148335f3a"
|
"Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/distribution/uuid",
|
||||||
|
"Comment": "v2.0.0-332-g79a4ca2",
|
||||||
|
"Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/term",
|
"ImportPath": "github.com/docker/docker/pkg/term",
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,84 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Domain represents a Version 2 domain
|
|
||||||
type Domain byte
|
|
||||||
|
|
||||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
|
||||||
const (
|
|
||||||
Person = Domain(0)
|
|
||||||
Group = Domain(1)
|
|
||||||
Org = Domain(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
|
||||||
//
|
|
||||||
// The domain should be one of Person, Group or Org.
|
|
||||||
// On a POSIX system the id should be the users UID for the Person
|
|
||||||
// domain and the users GID for the Group. The meaning of id for
|
|
||||||
// the domain Org or on non-POSIX systems is site defined.
|
|
||||||
//
|
|
||||||
// For a given domain/id pair the same token may be returned for up to
|
|
||||||
// 7 minutes and 10 seconds.
|
|
||||||
func NewDCESecurity(domain Domain, id uint32) UUID {
|
|
||||||
uuid := NewUUID()
|
|
||||||
if uuid != nil {
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
|
||||||
uuid[9] = byte(domain)
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
|
||||||
// domain with the id returned by os.Getuid.
|
|
||||||
//
|
|
||||||
// NewDCEPerson(Person, uint32(os.Getuid()))
|
|
||||||
func NewDCEPerson() UUID {
|
|
||||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
|
||||||
// domain with the id returned by os.Getgid.
|
|
||||||
//
|
|
||||||
// NewDCEGroup(Group, uint32(os.Getgid()))
|
|
||||||
func NewDCEGroup() UUID {
|
|
||||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain returns the domain for a Version 2 UUID or false.
|
|
||||||
func (uuid UUID) Domain() (Domain, bool) {
|
|
||||||
if v, _ := uuid.Version(); v != 2 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return Domain(uuid[9]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns the id for a Version 2 UUID or false.
|
|
||||||
func (uuid UUID) Id() (uint32, bool) {
|
|
||||||
if v, _ := uuid.Version(); v != 2 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint32(uuid[0:4]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Domain) String() string {
|
|
||||||
switch d {
|
|
||||||
case Person:
|
|
||||||
return "Person"
|
|
||||||
case Group:
|
|
||||||
return "Group"
|
|
||||||
case Org:
|
|
||||||
return "Org"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Domain%d", int(d))
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The uuid package generates and inspects UUIDs.
|
|
||||||
//
|
|
||||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
|
||||||
package uuid
|
|
|
@ -1,53 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Well known Name Space IDs and UUIDs
|
|
||||||
var (
|
|
||||||
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewHash returns a new UUID dervied from the hash of space concatenated with
|
|
||||||
// data generated by h. The hash should be at least 16 byte in length. The
|
|
||||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
|
||||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
|
||||||
// NewMD5 and NewSHA1.
|
|
||||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(space)
|
|
||||||
h.Write([]byte(data))
|
|
||||||
s := h.Sum(nil)
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
copy(uuid, s)
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
|
||||||
// supplied name space and data.
|
|
||||||
//
|
|
||||||
// NewHash(md5.New(), space, data, 3)
|
|
||||||
func NewMD5(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(md5.New(), space, data, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
|
||||||
// supplied name space and data.
|
|
||||||
//
|
|
||||||
// NewHash(sha1.New(), space, data, 5)
|
|
||||||
func NewSHA1(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(sha1.New(), space, data, 5)
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
func (u UUID) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(u) == 0 {
|
|
||||||
return []byte(`""`), nil
|
|
||||||
}
|
|
||||||
return []byte(`"` + u.String() + `"`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UUID) UnmarshalJSON(data []byte) error {
|
|
||||||
if len(data) == 0 || string(data) == `""` {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
|
|
||||||
return errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
data = data[1 : len(data)-1]
|
|
||||||
uu := Parse(string(data))
|
|
||||||
if uu == nil {
|
|
||||||
return errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
*u = uu
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
|
||||||
|
|
||||||
func TestJSON(t *testing.T) {
|
|
||||||
type S struct {
|
|
||||||
ID1 UUID
|
|
||||||
ID2 UUID
|
|
||||||
}
|
|
||||||
s1 := S{ID1: testUUID}
|
|
||||||
data, err := json.Marshal(&s1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var s2 S
|
|
||||||
if err := json.Unmarshal(data, &s2); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(&s1, &s2) {
|
|
||||||
t.Errorf("got %#v, want %#v", s2, s1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
var (
|
|
||||||
interfaces []net.Interface // cached list of interfaces
|
|
||||||
ifname string // name of interface being used
|
|
||||||
nodeID []byte // hardware for version 1 UUIDs
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeInterface returns the name of the interface from which the NodeID was
|
|
||||||
// derived. The interface "user" is returned if the NodeID was set by
|
|
||||||
// SetNodeID.
|
|
||||||
func NodeInterface() string {
|
|
||||||
return ifname
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
|
||||||
// If name is "" then the first usable interface found will be used or a random
|
|
||||||
// Node ID will be generated. If a named interface cannot be found then false
|
|
||||||
// is returned.
|
|
||||||
//
|
|
||||||
// SetNodeInterface never fails when name is "".
|
|
||||||
func SetNodeInterface(name string) bool {
|
|
||||||
if interfaces == nil {
|
|
||||||
var err error
|
|
||||||
interfaces, err = net.Interfaces()
|
|
||||||
if err != nil && name != "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ifs := range interfaces {
|
|
||||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
|
||||||
if setNodeID(ifs.HardwareAddr) {
|
|
||||||
ifname = ifs.Name
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We found no interfaces with a valid hardware address. If name
|
|
||||||
// does not specify a specific interface generate a random Node ID
|
|
||||||
// (section 4.1.6)
|
|
||||||
if name == "" {
|
|
||||||
if nodeID == nil {
|
|
||||||
nodeID = make([]byte, 6)
|
|
||||||
}
|
|
||||||
randomBits(nodeID)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
|
||||||
// if not already set.
|
|
||||||
func NodeID() []byte {
|
|
||||||
if nodeID == nil {
|
|
||||||
SetNodeInterface("")
|
|
||||||
}
|
|
||||||
nid := make([]byte, 6)
|
|
||||||
copy(nid, nodeID)
|
|
||||||
return nid
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
|
||||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
|
||||||
// Node ID is not set.
|
|
||||||
func SetNodeID(id []byte) bool {
|
|
||||||
if setNodeID(id) {
|
|
||||||
ifname = "user"
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func setNodeID(id []byte) bool {
|
|
||||||
if len(id) < 6 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if nodeID == nil {
|
|
||||||
nodeID = make([]byte, 6)
|
|
||||||
}
|
|
||||||
copy(nodeID, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
|
||||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) NodeID() []byte {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
node := make([]byte, 6)
|
|
||||||
copy(node, uuid[10:])
|
|
||||||
return node
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This test is only run when --regressions is passed on the go test line.
|
|
||||||
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
|
|
||||||
|
|
||||||
// TestClockSeqRace tests for a particular race condition of returning two
|
|
||||||
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
|
|
||||||
// condition, before being fixed, nearly always occured in under 30 seconds.
|
|
||||||
func TestClockSeqRace(t *testing.T) {
|
|
||||||
if !*regressions {
|
|
||||||
t.Skip("skipping regression tests")
|
|
||||||
}
|
|
||||||
duration := time.Minute
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
ch := make(chan UUID, 10000)
|
|
||||||
ncpu := runtime.NumCPU()
|
|
||||||
switch ncpu {
|
|
||||||
case 0, 1:
|
|
||||||
// We can't run the test effectively.
|
|
||||||
t.Skip("skipping race test, only one CPU detected")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
runtime.GOMAXPROCS(ncpu)
|
|
||||||
}
|
|
||||||
for i := 0; i < ncpu; i++ {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case ch <- NewUUID():
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
uuids := make(map[string]bool)
|
|
||||||
cnt := 0
|
|
||||||
start := time.Now()
|
|
||||||
for u := range ch {
|
|
||||||
s := u.String()
|
|
||||||
if uuids[s] {
|
|
||||||
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uuids[s] = true
|
|
||||||
if time.Since(start) > duration {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cnt++
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
// Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
|
||||||
// 1582.
|
|
||||||
type Time int64
|
|
||||||
|
|
||||||
const (
|
|
||||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
|
||||||
unix = 2440587 // Julian day of 1 Jan 1970
|
|
||||||
epoch = unix - lillian // Days between epochs
|
|
||||||
g1582 = epoch * 86400 // seconds between epochs
|
|
||||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mu sync.Mutex
|
|
||||||
lasttime uint64 // last time we returned
|
|
||||||
clock_seq uint16 // clock sequence for this run
|
|
||||||
|
|
||||||
timeNow = time.Now // for testing
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
|
||||||
// epoch of 1 Jan 1970.
|
|
||||||
func (t Time) UnixTime() (sec, nsec int64) {
|
|
||||||
sec = int64(t - g1582ns100)
|
|
||||||
nsec = (sec % 10000000) * 100
|
|
||||||
sec /= 10000000
|
|
||||||
return sec, nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
|
||||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
|
||||||
// is returned if the current time cannot be determined.
|
|
||||||
func GetTime() (Time, uint16, error) {
|
|
||||||
defer mu.Unlock()
|
|
||||||
mu.Lock()
|
|
||||||
return getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTime() (Time, uint16, error) {
|
|
||||||
t := timeNow()
|
|
||||||
|
|
||||||
// If we don't have a clock sequence already, set one.
|
|
||||||
if clock_seq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
|
||||||
|
|
||||||
// If time has gone backwards with this clock sequence then we
|
|
||||||
// increment the clock sequence
|
|
||||||
if now <= lasttime {
|
|
||||||
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
|
||||||
}
|
|
||||||
lasttime = now
|
|
||||||
return Time(now), clock_seq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the current clock sequence, generating one if not
|
|
||||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
|
||||||
//
|
|
||||||
// The uuid package does not use global static storage for the clock sequence or
|
|
||||||
// the last time a UUID was generated. Unless SetClockSequence a new random
|
|
||||||
// clock sequence is generated the first time a clock sequence is requested by
|
|
||||||
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
|
||||||
// for
|
|
||||||
func ClockSequence() int {
|
|
||||||
defer mu.Unlock()
|
|
||||||
mu.Lock()
|
|
||||||
return clockSequence()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clockSequence() int {
|
|
||||||
if clock_seq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
return int(clock_seq & 0x3fff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
|
||||||
// -1 causes a new sequence to be generated.
|
|
||||||
func SetClockSequence(seq int) {
|
|
||||||
defer mu.Unlock()
|
|
||||||
mu.Lock()
|
|
||||||
setClockSequence(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setClockSequence(seq int) {
|
|
||||||
if seq == -1 {
|
|
||||||
var b [2]byte
|
|
||||||
randomBits(b[:]) // clock sequence
|
|
||||||
seq = int(b[0])<<8 | int(b[1])
|
|
||||||
}
|
|
||||||
old_seq := clock_seq
|
|
||||||
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
|
||||||
if old_seq != clock_seq {
|
|
||||||
lasttime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
|
||||||
// uuid. It returns false if uuid is not valid. The time is only well defined
|
|
||||||
// for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) Time() (Time, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
|
||||||
return Time(time), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
|
||||||
// if uuid is not valid. The clock sequence is only well defined for version 1
|
|
||||||
// and 2 UUIDs.
|
|
||||||
func (uuid UUID) ClockSequence() (int, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// randomBits completely fills slice b with random data.
|
|
||||||
func randomBits(b []byte) {
|
|
||||||
if _, err := io.ReadFull(rander, b); err != nil {
|
|
||||||
panic(err.Error()) // rand should never fail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
|
||||||
var xvalues = []byte{
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
}
|
|
||||||
|
|
||||||
// xtob converts the the first two hex bytes of x into a byte.
|
|
||||||
func xtob(x string) (byte, bool) {
|
|
||||||
b1 := xvalues[x[0]]
|
|
||||||
b2 := xvalues[x[1]]
|
|
||||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
|
||||||
// 4122.
|
|
||||||
type UUID []byte
|
|
||||||
|
|
||||||
// A Version represents a UUIDs version.
|
|
||||||
type Version byte
|
|
||||||
|
|
||||||
// A Variant represents a UUIDs variant.
|
|
||||||
type Variant byte
|
|
||||||
|
|
||||||
// Constants returned by Variant.
|
|
||||||
const (
|
|
||||||
Invalid = Variant(iota) // Invalid UUID
|
|
||||||
RFC4122 // The variant specified in RFC4122
|
|
||||||
Reserved // Reserved, NCS backward compatibility.
|
|
||||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
|
||||||
Future // Reserved for future definition.
|
|
||||||
)
|
|
||||||
|
|
||||||
var rander = rand.Reader // random function
|
|
||||||
|
|
||||||
// New returns a new random (version 4) UUID as a string. It is a convenience
|
|
||||||
// function for NewRandom().String().
|
|
||||||
func New() string {
|
|
||||||
return NewRandom().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
|
||||||
func Parse(s string) UUID {
|
|
||||||
if len(s) == 36+9 {
|
|
||||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s = s[9:]
|
|
||||||
} else if len(s) != 36 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
for i, x := range []int{
|
|
||||||
0, 2, 4, 6,
|
|
||||||
9, 11,
|
|
||||||
14, 16,
|
|
||||||
19, 21,
|
|
||||||
24, 26, 28, 30, 32, 34} {
|
|
||||||
if v, ok := xtob(s[x:]); !ok {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
uuid[i] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns true if uuid1 and uuid2 are equal.
|
|
||||||
func Equal(uuid1, uuid2 UUID) bool {
|
|
||||||
return bytes.Equal(uuid1, uuid2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
// , or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) String() string {
|
|
||||||
if uuid == nil || len(uuid) != 16 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
b := []byte(uuid)
|
|
||||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
|
||||||
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// URN returns the RFC 2141 URN form of uuid,
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) URN() string {
|
|
||||||
if uuid == nil || len(uuid) != 16 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
b := []byte(uuid)
|
|
||||||
return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
|
|
||||||
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant returns the variant encoded in uuid. It returns Invalid if
|
|
||||||
// uuid is invalid.
|
|
||||||
func (uuid UUID) Variant() Variant {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return Invalid
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case (uuid[8] & 0xc0) == 0x80:
|
|
||||||
return RFC4122
|
|
||||||
case (uuid[8] & 0xe0) == 0xc0:
|
|
||||||
return Microsoft
|
|
||||||
case (uuid[8] & 0xe0) == 0xe0:
|
|
||||||
return Future
|
|
||||||
default:
|
|
||||||
return Reserved
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the verison of uuid. It returns false if uuid is not
|
|
||||||
// valid.
|
|
||||||
func (uuid UUID) Version() (Version, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return Version(uuid[6] >> 4), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
if v > 15 {
|
|
||||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("VERSION_%d", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Variant) String() string {
|
|
||||||
switch v {
|
|
||||||
case RFC4122:
|
|
||||||
return "RFC4122"
|
|
||||||
case Reserved:
|
|
||||||
return "Reserved"
|
|
||||||
case Microsoft:
|
|
||||||
return "Microsoft"
|
|
||||||
case Future:
|
|
||||||
return "Future"
|
|
||||||
case Invalid:
|
|
||||||
return "Invalid"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("BadVariant%d", int(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRand sets the random number generator to r, which implents io.Reader.
|
|
||||||
// If r.Read returns an error when the package requests random data then
|
|
||||||
// a panic will be issued.
|
|
||||||
//
|
|
||||||
// Calling SetRand with nil sets the random number generator to the default
|
|
||||||
// generator.
|
|
||||||
func SetRand(r io.Reader) {
|
|
||||||
if r == nil {
|
|
||||||
rander = rand.Reader
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rander = r
|
|
||||||
}
|
|
|
@ -1,390 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type test struct {
|
|
||||||
in string
|
|
||||||
version Version
|
|
||||||
variant Variant
|
|
||||||
isuuid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = []test{
|
|
||||||
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
|
|
||||||
|
|
||||||
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
|
|
||||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
|
|
||||||
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
|
|
||||||
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
|
|
||||||
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
|
|
||||||
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
|
|
||||||
|
|
||||||
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
|
|
||||||
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
|
|
||||||
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
|
|
||||||
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
|
|
||||||
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
|
|
||||||
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
var constants = []struct {
|
|
||||||
c interface{}
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{Person, "Person"},
|
|
||||||
{Group, "Group"},
|
|
||||||
{Org, "Org"},
|
|
||||||
{Invalid, "Invalid"},
|
|
||||||
{RFC4122, "RFC4122"},
|
|
||||||
{Reserved, "Reserved"},
|
|
||||||
{Microsoft, "Microsoft"},
|
|
||||||
{Future, "Future"},
|
|
||||||
{Domain(17), "Domain17"},
|
|
||||||
{Variant(42), "BadVariant42"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTest(t *testing.T, in string, tt test) {
|
|
||||||
uuid := Parse(in)
|
|
||||||
if ok := (uuid != nil); ok != tt.isuuid {
|
|
||||||
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
|
||||||
}
|
|
||||||
if uuid == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := uuid.Variant(); v != tt.variant {
|
|
||||||
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
|
|
||||||
}
|
|
||||||
if v, _ := uuid.Version(); v != tt.version {
|
|
||||||
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUUID(t *testing.T) {
|
|
||||||
for _, tt := range tests {
|
|
||||||
testTest(t, tt.in, tt)
|
|
||||||
testTest(t, strings.ToUpper(tt.in), tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConstants(t *testing.T) {
|
|
||||||
for x, tt := range constants {
|
|
||||||
v, ok := tt.c.(fmt.Stringer)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%x: %v: not a stringer", x, v)
|
|
||||||
} else if s := v.String(); s != tt.name {
|
|
||||||
v, _ := tt.c.(int)
|
|
||||||
t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRandomUUID(t *testing.T) {
|
|
||||||
m := make(map[string]bool)
|
|
||||||
for x := 1; x < 32; x++ {
|
|
||||||
uuid := NewRandom()
|
|
||||||
s := uuid.String()
|
|
||||||
if m[s] {
|
|
||||||
t.Errorf("NewRandom returned duplicated UUID %s\n", s)
|
|
||||||
}
|
|
||||||
m[s] = true
|
|
||||||
if v, _ := uuid.Version(); v != 4 {
|
|
||||||
t.Errorf("Random UUID of version %s\n", v)
|
|
||||||
}
|
|
||||||
if uuid.Variant() != RFC4122 {
|
|
||||||
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
m := make(map[string]bool)
|
|
||||||
for x := 1; x < 32; x++ {
|
|
||||||
s := New()
|
|
||||||
if m[s] {
|
|
||||||
t.Errorf("New returned duplicated UUID %s\n", s)
|
|
||||||
}
|
|
||||||
m[s] = true
|
|
||||||
uuid := Parse(s)
|
|
||||||
if uuid == nil {
|
|
||||||
t.Errorf("New returned %q which does not decode\n", s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v, _ := uuid.Version(); v != 4 {
|
|
||||||
t.Errorf("Random UUID of version %s\n", v)
|
|
||||||
}
|
|
||||||
if uuid.Variant() != RFC4122 {
|
|
||||||
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clockSeq(t *testing.T, uuid UUID) int {
|
|
||||||
seq, ok := uuid.ClockSequence()
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("%s: invalid clock sequence\n", uuid)
|
|
||||||
}
|
|
||||||
return seq
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClockSeq(t *testing.T) {
|
|
||||||
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
|
|
||||||
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
|
|
||||||
monTime := time.Now()
|
|
||||||
timeNow = func() time.Time {
|
|
||||||
monTime = monTime.Add(1 * time.Second)
|
|
||||||
return monTime
|
|
||||||
}
|
|
||||||
|
|
||||||
SetClockSequence(-1)
|
|
||||||
uuid1 := NewUUID()
|
|
||||||
uuid2 := NewUUID()
|
|
||||||
|
|
||||||
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
|
|
||||||
t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2))
|
|
||||||
}
|
|
||||||
|
|
||||||
SetClockSequence(-1)
|
|
||||||
uuid2 = NewUUID()
|
|
||||||
|
|
||||||
// Just on the very off chance we generated the same sequence
|
|
||||||
// two times we try again.
|
|
||||||
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
|
||||||
SetClockSequence(-1)
|
|
||||||
uuid2 = NewUUID()
|
|
||||||
}
|
|
||||||
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
|
||||||
t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1))
|
|
||||||
}
|
|
||||||
|
|
||||||
SetClockSequence(0x1234)
|
|
||||||
uuid1 = NewUUID()
|
|
||||||
if seq := clockSeq(t, uuid1); seq != 0x1234 {
|
|
||||||
t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoding(t *testing.T) {
|
|
||||||
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
|
||||||
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
|
||||||
data := UUID{
|
|
||||||
0x7d, 0x44, 0x48, 0x40,
|
|
||||||
0x9d, 0xc0,
|
|
||||||
0x11, 0xd1,
|
|
||||||
0xb2, 0x45,
|
|
||||||
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
|
|
||||||
}
|
|
||||||
if v := data.String(); v != text {
|
|
||||||
t.Errorf("%x: encoded to %s, expected %s\n", data, v, text)
|
|
||||||
}
|
|
||||||
if v := data.URN(); v != urn {
|
|
||||||
t.Errorf("%x: urn is %s, expected %s\n", data, v, urn)
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := Parse(text)
|
|
||||||
if !Equal(uuid, data) {
|
|
||||||
t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVersion1(t *testing.T) {
|
|
||||||
uuid1 := NewUUID()
|
|
||||||
uuid2 := NewUUID()
|
|
||||||
|
|
||||||
if Equal(uuid1, uuid2) {
|
|
||||||
t.Errorf("%s:duplicate uuid\n", uuid1)
|
|
||||||
}
|
|
||||||
if v, _ := uuid1.Version(); v != 1 {
|
|
||||||
t.Errorf("%s: version %s expected 1\n", uuid1, v)
|
|
||||||
}
|
|
||||||
if v, _ := uuid2.Version(); v != 1 {
|
|
||||||
t.Errorf("%s: version %s expected 1\n", uuid2, v)
|
|
||||||
}
|
|
||||||
n1 := uuid1.NodeID()
|
|
||||||
n2 := uuid2.NodeID()
|
|
||||||
if !bytes.Equal(n1, n2) {
|
|
||||||
t.Errorf("Different nodes %x != %x\n", n1, n2)
|
|
||||||
}
|
|
||||||
t1, ok := uuid1.Time()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: invalid time\n", uuid1)
|
|
||||||
}
|
|
||||||
t2, ok := uuid2.Time()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: invalid time\n", uuid2)
|
|
||||||
}
|
|
||||||
q1, ok := uuid1.ClockSequence()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: invalid clock sequence\n", uuid1)
|
|
||||||
}
|
|
||||||
q2, ok := uuid2.ClockSequence()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: invalid clock sequence", uuid2)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case t1 == t2 && q1 == q2:
|
|
||||||
t.Errorf("time stopped\n")
|
|
||||||
case t1 > t2 && q1 == q2:
|
|
||||||
t.Errorf("time reversed\n")
|
|
||||||
case t1 < t2 && q1 != q2:
|
|
||||||
t.Errorf("clock sequence chaned unexpectedly\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAndTime(t *testing.T) {
|
|
||||||
// Time is February 5, 1998 12:30:23.136364800 AM GMT
|
|
||||||
|
|
||||||
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
|
|
||||||
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
|
|
||||||
|
|
||||||
ts, ok := uuid.Time()
|
|
||||||
if ok {
|
|
||||||
c := time.Unix(ts.UnixTime())
|
|
||||||
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
|
|
||||||
if !c.Equal(want) {
|
|
||||||
t.Errorf("Got time %v, want %v", c, want)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s: bad time\n", uuid)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(node, uuid.NodeID()) {
|
|
||||||
t.Errorf("Expected node %v got %v\n", node, uuid.NodeID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMD5(t *testing.T) {
|
|
||||||
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
|
|
||||||
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
|
||||||
if uuid != want {
|
|
||||||
t.Errorf("MD5: got %q expected %q\n", uuid, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSHA1(t *testing.T) {
|
|
||||||
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
|
|
||||||
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
|
|
||||||
if uuid != want {
|
|
||||||
t.Errorf("SHA1: got %q expected %q\n", uuid, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeID(t *testing.T) {
|
|
||||||
nid := []byte{1, 2, 3, 4, 5, 6}
|
|
||||||
SetNodeInterface("")
|
|
||||||
s := NodeInterface()
|
|
||||||
if s == "" || s == "user" {
|
|
||||||
t.Errorf("NodeInterface %q after SetInteface\n", s)
|
|
||||||
}
|
|
||||||
node1 := NodeID()
|
|
||||||
if node1 == nil {
|
|
||||||
t.Errorf("NodeID nil after SetNodeInterface\n", s)
|
|
||||||
}
|
|
||||||
SetNodeID(nid)
|
|
||||||
s = NodeInterface()
|
|
||||||
if s != "user" {
|
|
||||||
t.Errorf("Expected NodeInterface %q got %q\n", "user", s)
|
|
||||||
}
|
|
||||||
node2 := NodeID()
|
|
||||||
if node2 == nil {
|
|
||||||
t.Errorf("NodeID nil after SetNodeID\n", s)
|
|
||||||
}
|
|
||||||
if bytes.Equal(node1, node2) {
|
|
||||||
t.Errorf("NodeID not changed after SetNodeID\n", s)
|
|
||||||
} else if !bytes.Equal(nid, node2) {
|
|
||||||
t.Errorf("NodeID is %x, expected %x\n", node2, nid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
|
|
||||||
if uuid == nil {
|
|
||||||
t.Errorf("%s failed\n", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if v, _ := uuid.Version(); v != 2 {
|
|
||||||
t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if v, ok := uuid.Domain(); !ok || v != domain {
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: %d: Domain failed\n", name, uuid)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, ok := uuid.Id(); !ok || v != id {
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: %d: Id failed\n", name, uuid)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDCE(t *testing.T) {
|
|
||||||
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
|
|
||||||
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
|
|
||||||
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type badRand struct{}
|
|
||||||
|
|
||||||
func (r badRand) Read(buf []byte) (int, error) {
|
|
||||||
for i, _ := range buf {
|
|
||||||
buf[i] = byte(i)
|
|
||||||
}
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadRand(t *testing.T) {
|
|
||||||
SetRand(badRand{})
|
|
||||||
uuid1 := New()
|
|
||||||
uuid2 := New()
|
|
||||||
if uuid1 != uuid2 {
|
|
||||||
t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2)
|
|
||||||
}
|
|
||||||
SetRand(nil)
|
|
||||||
uuid1 = New()
|
|
||||||
uuid2 = New()
|
|
||||||
if uuid1 == uuid2 {
|
|
||||||
t.Errorf("unexecpted duplicates, got %q\n", uuid1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
|
||||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
|
||||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
|
||||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
|
||||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
|
||||||
// return the current NewUUID returns nil.
|
|
||||||
func NewUUID() UUID {
|
|
||||||
if nodeID == nil {
|
|
||||||
SetNodeInterface("")
|
|
||||||
}
|
|
||||||
|
|
||||||
now, seq, err := GetTime()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
|
|
||||||
time_low := uint32(now & 0xffffffff)
|
|
||||||
time_mid := uint16((now >> 32) & 0xffff)
|
|
||||||
time_hi := uint16((now >> 48) & 0x0fff)
|
|
||||||
time_hi |= 0x1000 // Version 1
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
|
||||||
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
|
||||||
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
|
||||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
|
||||||
copy(uuid[10:], nodeID)
|
|
||||||
|
|
||||||
return uuid
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
// Random returns a Random (Version 4) UUID or panics.
|
|
||||||
//
|
|
||||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
|
||||||
// package.
|
|
||||||
//
|
|
||||||
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
|
||||||
//
|
|
||||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
|
||||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
|
||||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
|
||||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
|
||||||
// year and having one duplicate.
|
|
||||||
func NewRandom() UUID {
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
randomBits([]byte(uuid))
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
|
||||||
return uuid
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/go-uuid/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func (ic *instanceContext) Value(key interface{}) interface{} {
|
||||||
|
|
||||||
var background = &instanceContext{
|
var background = &instanceContext{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
id: uuid.New(),
|
id: uuid.Generate().String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. The background context
|
// Background returns a non-nil, empty Context. The background context
|
||||||
|
|
|
@ -8,14 +8,15 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common errors used with this package.
|
// Common errors used with this package.
|
||||||
var (
|
var (
|
||||||
ErrNoRequestContext = errors.New("no http request in context")
|
ErrNoRequestContext = errors.New("no http request in context")
|
||||||
|
ErrNoResponseWriterContext = errors.New("no http response in context")
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseIP(ipStr string) net.IP {
|
func parseIP(ipStr string) net.IP {
|
||||||
|
@ -78,7 +79,7 @@ func WithRequest(ctx Context, r *http.Request) Context {
|
||||||
return &httpRequestContext{
|
return &httpRequestContext{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
id: uuid.New(), // assign the request a unique.
|
id: uuid.Generate().String(),
|
||||||
r: r,
|
r: r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +111,20 @@ func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.Respo
|
||||||
return irw, irw
|
return irw, irw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseWriter returns the http.ResponseWriter from the provided
|
||||||
|
// context. If not present, ErrNoResponseWriterContext is returned. The
|
||||||
|
// returned instance provides instrumentation in the context.
|
||||||
|
func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
|
||||||
|
v := ctx.Value("http.response")
|
||||||
|
|
||||||
|
rw, ok := v.(http.ResponseWriter)
|
||||||
|
if !ok || rw == nil {
|
||||||
|
return nil, ErrNoResponseWriterContext
|
||||||
|
}
|
||||||
|
|
||||||
|
return rw, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getVarsFromRequest let's us change request vars implementation for testing
|
// getVarsFromRequest let's us change request vars implementation for testing
|
||||||
// and maybe future changes.
|
// and maybe future changes.
|
||||||
var getVarsFromRequest = mux.Vars
|
var getVarsFromRequest = mux.Vars
|
||||||
|
@ -287,7 +302,7 @@ func (irw *instrumentedResponseWriter) Flush() {
|
||||||
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||||
if keyStr, ok := key.(string); ok {
|
if keyStr, ok := key.(string); ok {
|
||||||
if keyStr == "http.response" {
|
if keyStr == "http.response" {
|
||||||
return irw.ResponseWriter
|
return irw
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(keyStr, "http.response.") {
|
if !strings.HasPrefix(keyStr, "http.response.") {
|
||||||
|
@ -307,9 +322,7 @@ func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||||
case "written":
|
case "written":
|
||||||
return irw.written
|
return irw.written
|
||||||
case "status":
|
case "status":
|
||||||
if irw.status != 0 {
|
return irw.status
|
||||||
return irw.status
|
|
||||||
}
|
|
||||||
case "contenttype":
|
case "contenttype":
|
||||||
contentType := irw.Header().Get("Content-Type")
|
contentType := irw.Header().Get("Content-Type")
|
||||||
if contentType != "" {
|
if contentType != "" {
|
||||||
|
|
|
@ -132,8 +132,21 @@ func TestWithResponseWriter(t *testing.T) {
|
||||||
trw := testResponseWriter{}
|
trw := testResponseWriter{}
|
||||||
ctx, rw := WithResponseWriter(Background(), &trw)
|
ctx, rw := WithResponseWriter(Background(), &trw)
|
||||||
|
|
||||||
if ctx.Value("http.response") != &trw {
|
if ctx.Value("http.response") != rw {
|
||||||
t.Fatalf("response not available in context: %v != %v", ctx.Value("http.response"), &trw)
|
t.Fatalf("response not available in context: %v != %v", ctx.Value("http.response"), rw)
|
||||||
|
}
|
||||||
|
|
||||||
|
grw, err := GetResponseWriter(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting response writer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if grw != rw {
|
||||||
|
t.Fatalf("unexpected response writer returned: %#v != %#v", grw, rw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Value("http.response.status") != 0 {
|
||||||
|
t.Fatalf("response status should always be a number and should be zero here: %v != 0", ctx.Value("http.response.status"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if n, err := rw.Write(make([]byte, 1024)); err != nil {
|
if n, err := rw.Write(make([]byte, 1024)); err != nil {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package context
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/uuid"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -99,3 +101,8 @@ func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
|
||||||
|
|
||||||
return logger.WithFields(fields)
|
return logger.WithFields(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// inject a logger into the uuid library.
|
||||||
|
uuid.Loggerf = GetLogger(Background()).Warnf
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithTrace allocates a traced timing span in a new context. This allows a
|
// WithTrace allocates a traced timing span in a new context. This allows a
|
||||||
|
@ -45,7 +45,7 @@ func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
|
||||||
f := runtime.FuncForPC(pc)
|
f := runtime.FuncForPC(pc)
|
||||||
ctx = &traced{
|
ctx = &traced{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
id: uuid.New(),
|
id: uuid.Generate().String(),
|
||||||
start: time.Now(),
|
start: time.Now(),
|
||||||
parent: GetStringValue(ctx, "trace.id"),
|
parent: GetStringValue(ctx, "trace.id"),
|
||||||
fnname: f.Name(),
|
fnname: f.Name(),
|
||||||
|
@ -54,9 +54,14 @@ func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx, func(format string, a ...interface{}) {
|
return ctx, func(format string, a ...interface{}) {
|
||||||
GetLogger(ctx, "trace.duration", "trace.id", "trace.parent.id",
|
GetLogger(ctx,
|
||||||
"trace.func", "trace.file", "trace.line").
|
"trace.duration",
|
||||||
Infof(format, a...) // info may be too chatty.
|
"trace.id",
|
||||||
|
"trace.parent.id",
|
||||||
|
"trace.func",
|
||||||
|
"trace.file",
|
||||||
|
"trace.line").
|
||||||
|
Debugf(format, a...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// An access controller has a simple interface with a single `Authorized`
|
// An access controller has a simple interface with a single `Authorized`
|
||||||
// method which checks that a given request is authorized to perform one or
|
// method which checks that a given request is authorized to perform one or
|
||||||
// more actions on one or more resources. This method should return a non-nil
|
// more actions on one or more resources. This method should return a non-nil
|
||||||
// error if the requset is not authorized.
|
// error if the request is not authorized.
|
||||||
//
|
//
|
||||||
// An implementation registers its access controller by name with a constructor
|
// An implementation registers its access controller by name with a constructor
|
||||||
// which accepts an options map for configuring the access controller.
|
// which accepts an options map for configuring the access controller.
|
||||||
|
@ -50,7 +50,7 @@ type Resource struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Access describes a specific action that is
|
// Access describes a specific action that is
|
||||||
// requested or allowed for a given recource.
|
// requested or allowed for a given resource.
|
||||||
type Access struct {
|
type Access struct {
|
||||||
Resource
|
Resource
|
||||||
Action string
|
Action string
|
||||||
|
@ -62,10 +62,10 @@ type Access struct {
|
||||||
type Challenge interface {
|
type Challenge interface {
|
||||||
error
|
error
|
||||||
// ServeHTTP prepares the request to conduct the appropriate challenge
|
// ServeHTTP prepares the request to conduct the appropriate challenge
|
||||||
// response. For most implementations, simply calling ServeHTTP should be
|
// response by adding the appropriate HTTP challenge header on the response
|
||||||
// sufficient. Because no body is written, users may write a custom body after
|
// message. Callers are expected to set the appropriate HTTP status code
|
||||||
// calling ServeHTTP, but any headers must be written before the call and may
|
// (e.g. 401) themselves. Because no body is written, users may write a
|
||||||
// be overwritten.
|
// custom body after calling ServeHTTP.
|
||||||
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
98
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access.go.orig
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access.go.orig
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Package basic provides a simple authentication scheme that checks for the
|
||||||
|
// user credential hash in an htpasswd formatted file in a configuration-determined
|
||||||
|
// location.
|
||||||
|
//
|
||||||
|
// The use of SHA hashes (htpasswd -s) is enforced since MD5 is insecure and simple
|
||||||
|
// system crypt() may be as well.
|
||||||
|
//
|
||||||
|
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
ctxu "github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accessController struct {
|
||||||
|
realm string
|
||||||
|
htpasswd *HTPasswd
|
||||||
|
}
|
||||||
|
|
||||||
|
type challenge struct {
|
||||||
|
realm string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ auth.AccessController = &accessController{}
|
||||||
|
var (
|
||||||
|
// ErrPasswordRequired - returned when no auth token is given.
|
||||||
|
<<<<<<< HEAD
|
||||||
|
ErrPasswordRequired = errors.New("authorization credential required")
|
||||||
|
// ErrInvalidCredential - returned when the auth token does not authenticate correctly.
|
||||||
|
=======
|
||||||
|
ErrPasswordRequired = errors.New("authorization credential required")
|
||||||
|
// ErrInvalidCredential - returned when the auth token does not authenticate correctly.
|
||||||
|
>>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings
|
||||||
|
ErrInvalidCredential = errors.New("invalid authorization credential")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
||||||
|
realm, present := options["realm"]
|
||||||
|
if _, ok := realm.(string); !present || !ok {
|
||||||
|
return nil, fmt.Errorf(`"realm" must be set for basic access controller`)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, present := options["path"]
|
||||||
|
if _, ok := path.(string); !present || !ok {
|
||||||
|
return nil, fmt.Errorf(`"path" must be set for basic access controller`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accessController{realm: realm.(string), htpasswd: NewHTPasswd(path.(string))}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
|
||||||
|
req, err := ctxu.GetRequest(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := req.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
challenge := challenge{
|
||||||
|
realm: ac.realm,
|
||||||
|
}
|
||||||
|
return nil, &challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
user, pass, ok := req.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Invalid Authorization header")
|
||||||
|
}
|
||||||
|
|
||||||
|
credential := strings.Split(string(text), ":")
|
||||||
|
if len(credential) != 2 {
|
||||||
|
challenge.err = ErrInvalidCredential
|
||||||
|
return nil, &challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.WithUser(ctx, auth.UserInfo{Name: user}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
header := fmt.Sprintf("Basic realm=%q", ch.realm)
|
||||||
|
w.Header().Set("WWW-Authenticate", header)
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *challenge) Error() string {
|
||||||
|
return fmt.Sprintf("basic authentication challenge: %#v", ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
auth.Register("basic", auth.InitFunc(newAccessController))
|
||||||
|
}
|
140
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access_test.go.orig
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access_test.go.orig
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicAccessController(t *testing.T) {
|
||||||
|
|
||||||
|
testRealm := "The-Shire"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
testUsers := []string{"bilbo", "frodo", "MiShil", "DeokMan"}
|
||||||
|
testPasswords := []string{"baggins", "baggins", "새주", "공주님"}
|
||||||
|
testHtpasswdContent := `bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
||||||
|
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
||||||
|
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
||||||
|
DeokMan:공주님`
|
||||||
|
=======
|
||||||
|
testUser := "bilbo"
|
||||||
|
testHtpasswdContent := "bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs="
|
||||||
|
>>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile("", "htpasswd-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create temporary htpasswd file")
|
||||||
|
}
|
||||||
|
if _, err = tempFile.WriteString(testHtpasswdContent); err != nil {
|
||||||
|
t.Fatal("could not write temporary htpasswd file")
|
||||||
|
}
|
||||||
|
|
||||||
|
options := map[string]interface{}{
|
||||||
|
"realm": testRealm,
|
||||||
|
"path": tempFile.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
accessController, err := newAccessController(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating access controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
var userNumber = 0
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.WithValue(nil, "http.request", r)
|
||||||
|
authCtx, err := accessController.Authorized(ctx)
|
||||||
|
if err != nil {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case auth.Challenge:
|
||||||
|
err.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected error authorizing request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, ok := authCtx.Value("auth.user").(auth.UserInfo)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("basic accessController did not set auth.user context")
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
if userInfo.Name != testUsers[userNumber] {
|
||||||
|
t.Fatalf("expected user name %q, got %q", testUsers[userNumber], userInfo.Name)
|
||||||
|
=======
|
||||||
|
if userInfo.Name != testUser {
|
||||||
|
t.Fatalf("expected user name %q, got %q", testUser, userInfo.Name)
|
||||||
|
>>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
CheckRedirect: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", server.URL, nil)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during GET: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Request should not be authorized
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("unexpected non-fail response status: %v != %v", resp.StatusCode, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
for i := 0; i < len(testUsers); i++ {
|
||||||
|
userNumber = i
|
||||||
|
req, _ = http.NewRequest("GET", server.URL, nil)
|
||||||
|
sekrit := testUsers[i] + ":" + testPasswords[i]
|
||||||
|
credential := "Basic " + base64.StdEncoding.EncodeToString([]byte(sekrit))
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", credential)
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during GET: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Request should be authorized
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("unexpected non-success response status: %v != %v for %s %s %s", resp.StatusCode, http.StatusNoContent, testUsers[i], testPasswords[i], credential)
|
||||||
|
}
|
||||||
|
=======
|
||||||
|
req, _ = http.NewRequest("GET", server.URL, nil)
|
||||||
|
|
||||||
|
sekrit := "bilbo:baggins"
|
||||||
|
credential := "Basic " + base64.StdEncoding.EncodeToString([]byte(sekrit))
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", credential)
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during GET: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Request should be authorized
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("unexpected non-success response status: %v != %v", resp.StatusCode, http.StatusNoContent)
|
||||||
|
>>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
155
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/htpasswd.go.orig
generated
vendored
Normal file
155
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/htpasswd.go.orig
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// AuthenticationFailureErr - a generic error message for authentication failure to be presented to agent.
|
||||||
|
var ErrAuthenticationFailure = errors.New("Bad username or password")
|
||||||
|
=======
|
||||||
|
// ErrSHARequired - returned in error field of challenge when the htpasswd was not made using SHA1 algorithm.
|
||||||
|
// (SHA1 is considered obsolete but the alternative for htpasswd is MD5, or system crypt...)
|
||||||
|
var ErrSHARequired = errors.New("htpasswd file must use SHA (htpasswd -s)")
|
||||||
|
>>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings
|
||||||
|
|
||||||
|
// HTPasswd - holds a path to a system .htpasswd file and the machinery to parse it.
|
||||||
|
type HTPasswd struct {
|
||||||
|
path string
|
||||||
|
reader *csv.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// AuthType represents a particular hash function used in the htpasswd file.
|
||||||
|
type AuthType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PlainText - Plain-text password storage (htpasswd -p)
|
||||||
|
PlainText AuthType = iota
|
||||||
|
// SHA1 - sha hashed password storage (htpasswd -s)
|
||||||
|
SHA1
|
||||||
|
// ApacheMD5 - apr iterated md5 hashing (htpasswd -m)
|
||||||
|
ApacheMD5
|
||||||
|
// BCrypt - BCrypt adapative password hashing (htpasswd -B)
|
||||||
|
BCrypt
|
||||||
|
// Crypt - System crypt() hashes. (htpasswd -d)
|
||||||
|
Crypt
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a text representation of the AuthType
|
||||||
|
func (at AuthType) String() string {
|
||||||
|
switch at {
|
||||||
|
case PlainText:
|
||||||
|
return "plaintext"
|
||||||
|
case SHA1:
|
||||||
|
return "sha1"
|
||||||
|
case ApacheMD5:
|
||||||
|
return "md5"
|
||||||
|
case BCrypt:
|
||||||
|
return "bcrypt"
|
||||||
|
case Crypt:
|
||||||
|
return "system crypt"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings
|
||||||
|
// NewHTPasswd - Create a new HTPasswd with the given path to .htpasswd file.
|
||||||
|
func NewHTPasswd(htpath string) *HTPasswd {
|
||||||
|
return &HTPasswd{path: htpath}
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
var bcryptPrefixRegexp = regexp.MustCompile(`^\$2[ab]?y\$`)
|
||||||
|
=======
|
||||||
|
// AuthenticateUser - Check a given user:password credential against the receiving HTPasswd's file.
|
||||||
|
func (htpasswd *HTPasswd) AuthenticateUser(user string, pwd string) (bool, error) {
|
||||||
|
>>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings
|
||||||
|
|
||||||
|
// GetAuthCredentialType - Inspect an htpasswd file credential and guess the encryption algorithm used.
|
||||||
|
func GetAuthCredentialType(cred string) AuthType {
|
||||||
|
if strings.HasPrefix(cred, "{SHA}") {
|
||||||
|
return SHA1
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(cred, "$apr1$") {
|
||||||
|
return ApacheMD5
|
||||||
|
}
|
||||||
|
if bcryptPrefixRegexp.MatchString(cred) {
|
||||||
|
return BCrypt
|
||||||
|
}
|
||||||
|
// There's just not a great way to distinguish between these next two...
|
||||||
|
if len(cred) == 13 {
|
||||||
|
return Crypt
|
||||||
|
}
|
||||||
|
return PlainText
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateUser - Check a given user:password credential against the receiving HTPasswd's file.
|
||||||
|
func (htpasswd *HTPasswd) AuthenticateUser(user string, pwd string) (bool, error) {
|
||||||
|
|
||||||
|
// Open the file.
|
||||||
|
in, err := os.Open(htpasswd.path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the contents of the standard .htpasswd until we hit the end or find a match.
|
||||||
|
reader := csv.NewReader(in)
|
||||||
|
reader.Comma = ':'
|
||||||
|
reader.Comment = '#'
|
||||||
|
reader.TrimLeadingSpace = true
|
||||||
|
for entry, readerr := reader.Read(); entry != nil || readerr != nil; entry, readerr = reader.Read() {
|
||||||
|
if readerr != nil {
|
||||||
|
return false, readerr
|
||||||
|
}
|
||||||
|
if len(entry) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry[0] == user {
|
||||||
|
credential := entry[1]
|
||||||
|
credType := GetAuthCredentialType(credential)
|
||||||
|
switch credType {
|
||||||
|
case SHA1:
|
||||||
|
{
|
||||||
|
sha := sha1.New()
|
||||||
|
sha.Write([]byte(pwd))
|
||||||
|
hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))
|
||||||
|
return entry[1][5:] == hash, nil
|
||||||
|
}
|
||||||
|
case ApacheMD5:
|
||||||
|
{
|
||||||
|
return false, errors.New(ApacheMD5.String() + " htpasswd hash function not yet supported")
|
||||||
|
}
|
||||||
|
case BCrypt:
|
||||||
|
{
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(credential), []byte(pwd))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case Crypt:
|
||||||
|
{
|
||||||
|
return false, errors.New(Crypt.String() + " htpasswd hash function not yet supported")
|
||||||
|
}
|
||||||
|
case PlainText:
|
||||||
|
{
|
||||||
|
if pwd == credential {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, ErrAuthenticationFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, ErrAuthenticationFailure
|
||||||
|
}
|
101
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/access.go
generated
vendored
Normal file
101
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/access.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Package htpasswd provides a simple authentication scheme that checks for the
|
||||||
|
// user credential hash in an htpasswd formatted file in a configuration-determined
|
||||||
|
// location.
|
||||||
|
//
|
||||||
|
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
|
||||||
|
package htpasswd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
ctxu "github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidCredential is returned when the auth token does not authenticate correctly.
|
||||||
|
ErrInvalidCredential = errors.New("invalid authorization credential")
|
||||||
|
|
||||||
|
// ErrAuthenticationFailure returned when authentication failure to be presented to agent.
|
||||||
|
ErrAuthenticationFailure = errors.New("authentication failured")
|
||||||
|
)
|
||||||
|
|
||||||
|
type accessController struct {
|
||||||
|
realm string
|
||||||
|
htpasswd *htpasswd
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ auth.AccessController = &accessController{}
|
||||||
|
|
||||||
|
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
||||||
|
realm, present := options["realm"]
|
||||||
|
if _, ok := realm.(string); !present || !ok {
|
||||||
|
return nil, fmt.Errorf(`"realm" must be set for htpasswd access controller`)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, present := options["path"]
|
||||||
|
if _, ok := path.(string); !present || !ok {
|
||||||
|
return nil, fmt.Errorf(`"path" must be set for htpasswd access controller`)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path.(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
h, err := newHTPasswd(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accessController{realm: realm.(string), htpasswd: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
|
||||||
|
req, err := ctxu.GetRequest(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password, ok := req.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
return nil, &challenge{
|
||||||
|
realm: ac.realm,
|
||||||
|
err: ErrInvalidCredential,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ac.htpasswd.authenticateUser(username, password); err != nil {
|
||||||
|
ctxu.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
|
||||||
|
return nil, &challenge{
|
||||||
|
realm: ac.realm,
|
||||||
|
err: ErrAuthenticationFailure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// challenge implements the auth.Challenge interface.
|
||||||
|
type challenge struct {
|
||||||
|
realm string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
header := fmt.Sprintf("Basic realm=%q", ch.realm)
|
||||||
|
w.Header().Set("WWW-Authenticate", header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *challenge) Error() string {
|
||||||
|
return fmt.Sprintf("basic authentication challenge: %#v", ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
auth.Register("htpasswd", auth.InitFunc(newAccessController))
|
||||||
|
}
|
122
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/access_test.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/access_test.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
package htpasswd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicAccessController(t *testing.T) {
|
||||||
|
testRealm := "The-Shire"
|
||||||
|
testUsers := []string{"bilbo", "frodo", "MiShil", "DeokMan"}
|
||||||
|
testPasswords := []string{"baggins", "baggins", "새주", "공주님"}
|
||||||
|
testHtpasswdContent := `bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
||||||
|
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
||||||
|
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
||||||
|
DeokMan:공주님`
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile("", "htpasswd-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create temporary htpasswd file")
|
||||||
|
}
|
||||||
|
if _, err = tempFile.WriteString(testHtpasswdContent); err != nil {
|
||||||
|
t.Fatal("could not write temporary htpasswd file")
|
||||||
|
}
|
||||||
|
|
||||||
|
options := map[string]interface{}{
|
||||||
|
"realm": testRealm,
|
||||||
|
"path": tempFile.Name(),
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
accessController, err := newAccessController(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating access controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
var userNumber = 0
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.WithRequest(ctx, r)
|
||||||
|
authCtx, err := accessController.Authorized(ctx)
|
||||||
|
if err != nil {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case auth.Challenge:
|
||||||
|
err.ServeHTTP(w, r)
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected error authorizing request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, ok := authCtx.Value("auth.user").(auth.UserInfo)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("basic accessController did not set auth.user context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if userInfo.Name != testUsers[userNumber] {
|
||||||
|
t.Fatalf("expected user name %q, got %q", testUsers[userNumber], userInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
CheckRedirect: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", server.URL, nil)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during GET: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Request should not be authorized
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("unexpected non-fail response status: %v != %v", resp.StatusCode, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonbcrypt := map[string]struct{}{
|
||||||
|
"bilbo": {},
|
||||||
|
"DeokMan": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(testUsers); i++ {
|
||||||
|
userNumber = i
|
||||||
|
req, err := http.NewRequest("GET", server.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error allocating new request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(testUsers[i], testPasswords[i])
|
||||||
|
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during GET: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if _, ok := nonbcrypt[testUsers[i]]; ok {
|
||||||
|
// these are not allowed.
|
||||||
|
// Request should be authorized
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusUnauthorized, testUsers[i], testPasswords[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Request should be authorized
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusNoContent, testUsers[i], testPasswords[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
80
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/htpasswd.go
generated
vendored
Normal file
80
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/htpasswd.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package htpasswd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// htpasswd holds a path to a system .htpasswd file and the machinery to parse
|
||||||
|
// it. Only bcrypt hash entries are supported.
|
||||||
|
type htpasswd struct {
|
||||||
|
entries map[string][]byte // maps username to password byte slice.
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHTPasswd parses the reader and returns an htpasswd or an error.
|
||||||
|
func newHTPasswd(rd io.Reader) (*htpasswd, error) {
|
||||||
|
entries, err := parseHTPasswd(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &htpasswd{entries: entries}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateUser checks a given user:password credential against the
|
||||||
|
// receiving HTPasswd's file. If the check passes, nil is returned.
|
||||||
|
func (htpasswd *htpasswd) authenticateUser(username string, password string) error {
|
||||||
|
credentials, ok := htpasswd.entries[username]
|
||||||
|
if !ok {
|
||||||
|
// timing attack paranoia
|
||||||
|
bcrypt.CompareHashAndPassword([]byte{}, []byte(password))
|
||||||
|
|
||||||
|
return ErrAuthenticationFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(credentials), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
return ErrAuthenticationFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHTPasswd parses the contents of htpasswd. This will read all the
|
||||||
|
// entries in the file, whether or not they are needed. An error is returned
|
||||||
|
// if an syntax errors are encountered or if the reader fails.
|
||||||
|
func parseHTPasswd(rd io.Reader) (map[string][]byte, error) {
|
||||||
|
entries := map[string][]byte{}
|
||||||
|
scanner := bufio.NewScanner(rd)
|
||||||
|
var line int
|
||||||
|
for scanner.Scan() {
|
||||||
|
line++ // 1-based line numbering
|
||||||
|
t := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
if len(t) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// lines that *begin* with a '#' are considered comments
|
||||||
|
if t[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.Index(t, ":")
|
||||||
|
if i < 0 || i >= len(t) {
|
||||||
|
return nil, fmt.Errorf("htpasswd: invalid entry at line %d: %q", line, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[t[:i]] = []byte(t[i+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
85
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/htpasswd_test.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/htpasswd/htpasswd_test.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package htpasswd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseHTPasswd(t *testing.T) {
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
err error
|
||||||
|
entries map[string][]byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
input: `
|
||||||
|
# This is a comment in a basic example.
|
||||||
|
bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
||||||
|
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
||||||
|
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
||||||
|
DeokMan:공주님
|
||||||
|
`,
|
||||||
|
entries: map[string][]byte{
|
||||||
|
"bilbo": []byte("{SHA}5siv5c0SHx681xU6GiSx9ZQryqs="),
|
||||||
|
"frodo": []byte("$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W"),
|
||||||
|
"MiShil": []byte("$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2"),
|
||||||
|
"DeokMan": []byte("공주님"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ensures comments are filtered",
|
||||||
|
input: `
|
||||||
|
# asdf:asdf
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ensure midline hash is not comment",
|
||||||
|
input: `
|
||||||
|
asdf:as#df
|
||||||
|
`,
|
||||||
|
entries: map[string][]byte{
|
||||||
|
"asdf": []byte("as#df"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ensure midline hash is not comment",
|
||||||
|
input: `
|
||||||
|
# A valid comment
|
||||||
|
valid:entry
|
||||||
|
asdf
|
||||||
|
`,
|
||||||
|
err: fmt.Errorf(`htpasswd: invalid entry at line 4: "asdf"`),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
|
||||||
|
entries, err := parseHTPasswd(strings.NewReader(tc.input))
|
||||||
|
if err != tc.err {
|
||||||
|
if tc.err == nil {
|
||||||
|
t.Fatalf("%s: unexpected error: %v", tc.desc, err)
|
||||||
|
} else {
|
||||||
|
if err.Error() != tc.err.Error() { // use string equality here.
|
||||||
|
t.Fatalf("%s: expected error not returned: %v != %v", tc.desc, err, tc.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.err != nil {
|
||||||
|
continue // don't test output
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow empty and nil to be equal
|
||||||
|
if tc.entries == nil {
|
||||||
|
tc.entries = map[string][]byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(entries, tc.entries) {
|
||||||
|
t.Fatalf("%s: entries not parsed correctly: %v != %v", tc.desc, entries, tc.entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/silly/access.go
generated
vendored
3
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/silly/access.go
generated
vendored
|
@ -66,7 +66,7 @@ func (ac *accessController) Authorized(ctx context.Context, accessRecords ...aut
|
||||||
return nil, &challenge
|
return nil, &challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.WithValue(ctx, "auth.user", auth.UserInfo{Name: "silly"}), nil
|
return auth.WithUser(ctx, auth.UserInfo{Name: "silly"}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type challenge struct {
|
type challenge struct {
|
||||||
|
@ -83,7 +83,6 @@ func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("WWW-Authenticate", header)
|
w.Header().Set("WWW-Authenticate", header)
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *challenge) Error() string {
|
func (ch *challenge) Error() string {
|
||||||
|
|
|
@ -22,6 +22,7 @@ func TestSillyAccessController(t *testing.T) {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case auth.Challenge:
|
case auth.Challenge:
|
||||||
err.ServeHTTP(w, r)
|
err.ServeHTTP(w, r)
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected error authorizing request: %v", err)
|
t.Fatalf("unexpected error authorizing request: %v", err)
|
||||||
|
|
|
@ -117,10 +117,9 @@ func (ac *authChallenge) SetHeader(header http.Header) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHttp handles writing the challenge response
|
// ServeHttp handles writing the challenge response
|
||||||
// by setting the challenge header and status code.
|
// by setting the challenge header.
|
||||||
func (ac *authChallenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (ac *authChallenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
ac.SetHeader(w.Header())
|
ac.SetHeader(w.Header())
|
||||||
w.WriteHeader(ac.Status())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// accessController implements the auth.AccessController interface.
|
// accessController implements the auth.AccessController interface.
|
||||||
|
|
2
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/token/util.go
generated
vendored
2
Godeps/_workspace/src/github.com/docker/distribution/registry/auth/token/util.go
generated
vendored
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// joseBase64UrlEncode encodes the given data using the standard base64 url
|
// joseBase64UrlEncode encodes the given data using the standard base64 url
|
||||||
// encoding format but with all trailing '=' characters ommitted in accordance
|
// encoding format but with all trailing '=' characters omitted in accordance
|
||||||
// with the jose specification.
|
// with the jose specification.
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
||||||
func joseBase64UrlEncode(b []byte) string {
|
func joseBase64UrlEncode(b []byte) string {
|
||||||
|
|
125
Godeps/_workspace/src/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
125
Godeps/_workspace/src/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// Package uuid provides simple UUID generation. Only version 4 style UUIDs
|
||||||
|
// can be generated.
|
||||||
|
//
|
||||||
|
// Please see http://tools.ietf.org/html/rfc4122 for details on UUIDs.
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Bits is the number of bits in a UUID
|
||||||
|
Bits = 128
|
||||||
|
|
||||||
|
// Size is the number of bytes in a UUID
|
||||||
|
Size = Bits / 8
|
||||||
|
|
||||||
|
format = "%08x-%04x-%04x-%04x-%012x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrUUIDInvalid indicates a parsed string is not a valid uuid.
|
||||||
|
ErrUUIDInvalid = fmt.Errorf("invalid uuid")
|
||||||
|
|
||||||
|
// Loggerf can be used to override the default logging destination. Such
|
||||||
|
// log messages in this library should be logged at warning or higher.
|
||||||
|
Loggerf = log.Printf
|
||||||
|
)
|
||||||
|
|
||||||
|
// UUID represents a UUID value. UUIDs can be compared and set to other values
|
||||||
|
// and accessed by byte.
|
||||||
|
type UUID [Size]byte
|
||||||
|
|
||||||
|
// Generate creates a new, version 4 uuid.
|
||||||
|
func Generate() (u UUID) {
|
||||||
|
const (
|
||||||
|
// ensures we backoff for less than 450ms total. Use the following to
|
||||||
|
// select new value, in units of 10ms:
|
||||||
|
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
|
||||||
|
maxretries = 9
|
||||||
|
backoff = time.Millisecond * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
totalBackoff time.Duration
|
||||||
|
retries int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// This should never block but the read may fail. Because of this,
|
||||||
|
// we just try to read the random number generator until we get
|
||||||
|
// something. This is a very rare condition but may happen.
|
||||||
|
b := time.Duration(retries) * backoff
|
||||||
|
time.Sleep(b)
|
||||||
|
totalBackoff += b
|
||||||
|
|
||||||
|
_, err := io.ReadFull(rand.Reader, u[:])
|
||||||
|
if err != nil {
|
||||||
|
if retryOnError(err) && retries < maxretries {
|
||||||
|
retries++
|
||||||
|
Loggerf("error generating version 4 uuid, retrying: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other errors represent a system problem. What did someone
|
||||||
|
// do to /dev/urandom?
|
||||||
|
panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
u[6] = (u[6] & 0x0f) | 0x40 // set version byte
|
||||||
|
u[8] = (u[8] & 0x3f) | 0x80 // set high order byte 0b10{8,9,a,b}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse attempts to extract a uuid from the string or returns an error.
|
||||||
|
func Parse(s string) (u UUID, err error) {
|
||||||
|
if len(s) != 36 {
|
||||||
|
return UUID{}, ErrUUIDInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// create stack addresses for each section of the uuid.
|
||||||
|
p := make([][]byte, 5)
|
||||||
|
|
||||||
|
if _, err := fmt.Sscanf(s, format, &p[0], &p[1], &p[2], &p[3], &p[4]); err != nil {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(u[0:4], p[0])
|
||||||
|
copy(u[4:6], p[1])
|
||||||
|
copy(u[6:8], p[2])
|
||||||
|
copy(u[8:10], p[3])
|
||||||
|
copy(u[10:16], p[4])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UUID) String() string {
|
||||||
|
return fmt.Sprintf(format, u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryOnError tries to detect whether or not retrying would be fruitful.
|
||||||
|
func retryOnError(err error) bool {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *os.PathError:
|
||||||
|
return retryOnError(err.Err) // unpack the target error
|
||||||
|
case syscall.Errno:
|
||||||
|
if err == syscall.EPERM {
|
||||||
|
// EPERM represents an entropy pool exhaustion, a condition under
|
||||||
|
// which we backoff and retry.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
48
Godeps/_workspace/src/github.com/docker/distribution/uuid/uuid_test.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/docker/distribution/uuid/uuid_test.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const iterations = 1000
|
||||||
|
|
||||||
|
func TestUUID4Generation(t *testing.T) {
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
u := Generate()
|
||||||
|
|
||||||
|
if u[6]&0xf0 != 0x40 {
|
||||||
|
t.Fatalf("version byte not correctly set: %v, %08b %08b", u, u[6], u[6]&0xf0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u[8]&0xc0 != 0x80 {
|
||||||
|
t.Fatalf("top order 8th byte not correctly set: %v, %b", u, u[8])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAndEquality(t *testing.T) {
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
u := Generate()
|
||||||
|
|
||||||
|
parsed, err := Parse(u.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing uuid %v: %v", u, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed != u {
|
||||||
|
t.Fatalf("parsing round trip failed: %v != %v", parsed, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range []string{
|
||||||
|
"bad",
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // correct length, incorrect format
|
||||||
|
" 20cc7775-2671-43c7-8742-51d1cfa23258", // leading space
|
||||||
|
"20cc7775-2671-43c7-8742-51d1cfa23258 ", // trailing space
|
||||||
|
"00000000-0000-0000-0000-x00000000000", // out of range character
|
||||||
|
} {
|
||||||
|
if _, err := Parse(c); err == nil {
|
||||||
|
t.Fatalf("parsing %q should have failed", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileChangelist stores all the changes as files
|
// FileChangelist stores all the changes as files
|
||||||
|
@ -69,7 +69,7 @@ func (cl FileChangelist) Add(c Change) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.New())
|
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
|
||||||
return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644)
|
return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue