diff --git a/client/build.go b/client/build.go index c47b2e2..ce9fc65 100644 --- a/client/build.go +++ b/client/build.go @@ -12,7 +12,6 @@ import ( "fmt" "net/http" - "github.com/globalsign/mgo/bson" "github.com/golang/glog" "github.com/gorilla/websocket" jsonresp "github.com/sylabs/json-resp" @@ -48,7 +47,7 @@ func (c *Client) SubmitBuild(ctx context.Context, d Definition, libraryRef strin err = jsonresp.ReadResponse(res.Body, &rd) if err == nil { glog.V(2).Infof("Build response - id: %s, wsurl: %s, libref: %s", - rd.ID.Hex(), rd.WSURL, rd.LibraryRef) + rd.ID, rd.WSURL, rd.LibraryRef) } return } @@ -95,8 +94,8 @@ func (c *Client) StreamOutput(ctx context.Context, wsURL string) error { } // GetBuildStatus gets the status of a build from the Remote Build Service -func (c *Client) GetBuildStatus(ctx context.Context, buildID bson.ObjectId) (rd ResponseData, err error) { - req, err := c.newRequest(http.MethodGet, "/v1/build/"+buildID.Hex(), "", nil) +func (c *Client) GetBuildStatus(ctx context.Context, buildID string) (rd ResponseData, err error) { + req, err := c.newRequest(http.MethodGet, "/v1/build/"+buildID, "", nil) if err != nil { return } diff --git a/client/build_test.go b/client/build_test.go index 76af699..2411e25 100644 --- a/client/build_test.go +++ b/client/build_test.go @@ -18,7 +18,6 @@ import ( "testing" "time" - "github.com/globalsign/mgo/bson" "github.com/gorilla/websocket" jsonresp "github.com/sylabs/json-resp" ) @@ -48,11 +47,11 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func newResponse(m *mockService, id bson.ObjectId, d Definition, libraryRef string) ResponseData { +func newResponse(m *mockService, id string, d Definition, libraryRef string) ResponseData { wsURL := url.URL{ Scheme: "ws", Host: m.httpAddr, - Path: fmt.Sprintf("%s%s", wsPath, id.Hex()), + Path: fmt.Sprintf("%s%s", wsPath, id), } libraryURL := url.URL{ Scheme: "http", @@ -82,7 +81,7 @@ func (m *mockService) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.t.Fatalf("failed to parse request: %v", err) } if m.buildResponseCode == http.StatusCreated { - id := bson.NewObjectId() + id := newObjectID() if err := jsonresp.WriteResponse(w, newResponse(m, id, rd.Definition, rd.LibraryRef), m.buildResponseCode); err != nil { m.t.Fatal(err) } @@ -94,11 +93,11 @@ func (m *mockService) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if r.Method == http.MethodGet && strings.HasPrefix(r.RequestURI, buildPath) { // Mock status endpoint id := r.RequestURI[strings.LastIndexByte(r.RequestURI, '/')+1:] - if !bson.IsObjectIdHex(id) { + if id == "" { m.t.Fatalf("failed to parse ID '%v'", id) } if m.statusResponseCode == http.StatusOK { - if err := jsonresp.WriteResponse(w, newResponse(m, bson.ObjectIdHex(id), Definition{}, ""), m.statusResponseCode); err != nil { + if err := jsonresp.WriteResponse(w, newResponse(m, id, Definition{}, ""), m.statusResponseCode); err != nil { m.t.Fatal(err) } } else { @@ -310,7 +309,7 @@ func TestDoBuildRequest(t *testing.T) { if err != nil { t.Fatalf("unexpected failure: %v", err) } - if !rd.ID.Valid() { + if rd.ID == "" { t.Fatalf("invalid ID") } if rd.WSURL == "" { @@ -367,7 +366,7 @@ func TestDoStatusRequest(t *testing.T) { } // ID to test with - id := bson.NewObjectId() + id := newObjectID() // Loop over test cases for _, tt := range tests { diff --git a/client/object_id_test.go b/client/object_id_test.go new file mode 100644 index 0000000..728c8a4 --- /dev/null +++ b/client/object_id_test.go @@ -0,0 +1,68 @@ +// Copyright (c) 2018-2019, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE.md file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package client + +import ( + "crypto/md5" + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "os" + "sync/atomic" + "time" +) + +var ( + objectIdCounter = readRandomUint32() + machineId = readMachineId() + processId = os.Getpid() +) + +func readRandomUint32() uint32 { + var b [4]byte + _, err := io.ReadFull(rand.Reader, b[:]) + if err != nil { + panic(fmt.Errorf("cannot read random object id: %v", err)) + } + return uint32((uint32(b[0]) << 0) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)) +} + +func readMachineId() []byte { + var sum [3]byte + id := sum[:] + hostname, err1 := os.Hostname() + if err1 != nil { + _, err2 := io.ReadFull(rand.Reader, id) + if err2 != nil { + panic(fmt.Errorf("cannot get hostname: %v; %v", err1, err2)) + } + return id + } + hw := md5.New() + hw.Write([]byte(hostname)) + copy(id, hw.Sum(nil)) + return id +} + +func newObjectID() string { + var b [12]byte + // Timestamp, 4 bytes, big endian + binary.BigEndian.PutUint32(b[:], uint32(time.Now().Unix())) + // Machine, first 3 bytes of md5(hostname) + b[4] = machineId[0] + b[5] = machineId[1] + b[6] = machineId[2] + // Pid, 2 bytes, specs don't specify endianness, but we use big endian. + b[7] = byte(processId >> 8) + b[8] = byte(processId) + // Increment, 3 bytes, big endian + i := atomic.AddUint32(&objectIdCounter, 1) + b[9] = byte(i >> 16) + b[10] = byte(i >> 8) + b[11] = byte(i) + return string(b[:]) +} diff --git a/client/types.go b/client/types.go index a998957..dd123b2 100644 --- a/client/types.go +++ b/client/types.go @@ -7,8 +7,6 @@ package client import ( "time" - - "github.com/globalsign/mgo/bson" ) // RequestData contains the info necessary for submitting a build to a remote service @@ -21,17 +19,17 @@ type RequestData struct { // ResponseData contains the details of an individual build type ResponseData struct { - ID bson.ObjectId `json:"id"` - CreatedBy string `json:"createdBy"` - SubmitTime time.Time `json:"submitTime"` - StartTime *time.Time `json:"startTime,omitempty" bson:",omitempty"` - IsComplete bool `json:"isComplete"` - CompleteTime *time.Time `json:"completeTime,omitempty"` - ImageSize int64 `json:"imageSize,omitempty"` - ImageChecksum string `json:"imageChecksum,omitempty"` - Definition Definition `json:"definition"` - WSURL string `json:"wsURL,omitempty" bson:"-"` - LibraryRef string `json:"libraryRef"` - LibraryURL string `json:"libraryURL"` - CallbackURL string `json:"callbackURL"` + ID string `json:"id"` + CreatedBy string `json:"createdBy"` + SubmitTime time.Time `json:"submitTime"` + StartTime *time.Time `json:"startTime,omitempty"` + IsComplete bool `json:"isComplete"` + CompleteTime *time.Time `json:"completeTime,omitempty"` + ImageSize int64 `json:"imageSize,omitempty"` + ImageChecksum string `json:"imageChecksum,omitempty"` + Definition Definition `json:"definition"` + WSURL string `json:"wsURL,omitempty"` + LibraryRef string `json:"libraryRef"` + LibraryURL string `json:"libraryURL"` + CallbackURL string `json:"callbackURL"` } diff --git a/go.mod b/go.mod index 1299095..4f3c3a3 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,7 @@ module github.com/sylabs/scs-build-client go 1.12 require ( - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/gorilla/websocket v1.4.0 - github.com/kr/pretty v0.1.0 // indirect github.com/sylabs/json-resp v0.5.0 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index 198a078..04ab81b 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,6 @@ -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/sylabs/json-resp v0.5.0 h1:AWdKu6aS0WrkkltX1M0ex0lENrIcx5TISox902s2L2M= github.com/sylabs/json-resp v0.5.0/go.mod h1:anCzED2SGHHZQDubMuoVtwMuJZdpqQ+7iso8yDFm/nQ= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=