Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Registry support #1381

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/dep/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestIntegration(t *testing.T) {

test.NeedsExternalNetwork(t)
test.NeedsGit(t)
test.SetupRegistry(t)

wd, err := os.Getwd()
if err != nil {
Expand Down Expand Up @@ -76,7 +77,7 @@ func TestDepCachedir(t *testing.T) {

t.Run("env-cachedir", func(t *testing.T) {
t.Parallel()
testProj := integration.NewTestProject(t, initPath, wd, runMain)
testProj := integration.NewTestProject(t, initPath, wd, nil, runMain)
defer testProj.Cleanup()

testProj.TempDir("cachedir")
Expand Down Expand Up @@ -104,7 +105,7 @@ func TestDepCachedir(t *testing.T) {
})
t.Run("env-invalid-cachedir", func(t *testing.T) {
t.Parallel()
testProj := integration.NewTestProject(t, initPath, wd, runMain)
testProj := integration.NewTestProject(t, initPath, wd, nil, runMain)
defer testProj.Cleanup()

var d []byte
Expand Down Expand Up @@ -184,7 +185,7 @@ func testIntegration(name, relPath, wd string, run integration.RunFunc) func(t *

// Set up environment
testCase := integration.NewTestCase(t, filepath.Join(wd, relPath), name)
testProj := integration.NewTestProject(t, testCase.InitialPath(), wd, run)
testProj := integration.NewTestProject(t, testCase.InitialPath(), wd, testCase.Env, run)
defer testProj.Cleanup()

// Create and checkout the vendor revisions
Expand Down
15 changes: 15 additions & 0 deletions cmd/dep/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import (
"text/tabwriter"

"github.com/golang/dep"
"github.com/golang/dep/gps"
"github.com/golang/dep/internal/fs"
"net/url"
)

var (
Expand Down Expand Up @@ -203,6 +205,19 @@ func (c *Config) Run() int {
Cachedir: cachedir,
}

registryURL := getEnv(c.Env, "DEPREGISTRYURL")
token := getEnv(c.Env, "DEPREGISTRYTOKEN")

// If the url or the token is not defined do not create registry config
if registryURL != "" && token != "" {
registryUrl, err := url.Parse(registryURL)
if err != nil {
errLogger.Printf("%v\n", err)
return errorExitCode
}
ctx.Registry = gps.NewRegistryConfig(registryUrl, token)
}

GOPATHS := filepath.SplitList(getEnv(c.Env, "GOPATH"))
ctx.SetPaths(c.WorkingDir, GOPATHS...)

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

[[constraint]]
name = "github.com/sdboyer/deptesttres"
version = "1.0.0"

[[constraint]]
name = "github.com/sdboyer/deptest"
version = "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"github.com/sdboyer/deptesttres"
)

func main() {
type a deptesttres.Bar
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"env": {
"DEPREGISTRYURL": "http://localhost:9090",
"DEPREGISTRYTOKEN": "2erygdasE45rty5JKwewrr75cb15rdeE"
},
"commands": [
["init", "-no-examples"],
["ensure", "-add", "github.com/sdboyer/deptest"]
],
"vendor-final": [
"github.com/sdboyer/deptest",
"github.com/sdboyer/deptesttres"
]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

[[constraint]]
name = "github.com/sdboyer/deptest"
version = "1.0.0"

[[constraint]]
name = "github.com/sdboyer/deptestdos"
version = "2.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package foo

import "github.com/sdboyer/deptest"

func Foo() deptest.Foo {
var y deptest.Foo

return y
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"

"github.com/golang/notexist/foo"
"github.com/sdboyer/deptestdos"
)

func main() {
var x deptestdos.Bar
y := foo.FooFunc()

fmt.Println(x, y)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"env": {
"DEPREGISTRYURL": "http://localhost:9090",
"DEPREGISTRYTOKEN": "2erygdasE45rty5JKwewrr75cb15rdeE"
},
"commands": [
["init", "-no-examples"]
],
"error-expected": "",
"vendor-final": [
"github.com/sdboyer/deptest",
"github.com/sdboyer/deptestdos"
]
}
2 changes: 2 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Ctx struct {
Verbose bool // Enables more verbose logging.
DisableLocking bool // When set, no lock file will be created to protect against simultaneous dep processes.
Cachedir string // Cache directory loaded from environment.
Registry gps.Registry
}

// SetPaths sets the WorkingDir and GOPATHs fields. If GOPATHs is empty, then
Expand Down Expand Up @@ -102,6 +103,7 @@ func (c *Ctx) SourceManager() (*gps.SourceMgr, error) {
Cachedir: cachedir,
Logger: c.Out,
DisableLocking: c.DisableLocking,
Registry: c.Registry,
})
}

Expand Down
136 changes: 134 additions & 2 deletions gps/deduce.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"strings"
"sync"

radix "github.com/armon/go-radix"
"github.com/armon/go-radix"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -561,7 +561,7 @@ type deductionCoordinator struct {
deducext *deducerTrie
}

func newDeductionCoordinator(superv *supervisor) *deductionCoordinator {
func newDeductionCoordinator(superv *supervisor) deducer {
dc := &deductionCoordinator{
suprvsr: superv,
rootxt: radix.New(),
Expand All @@ -571,6 +571,85 @@ func newDeductionCoordinator(superv *supervisor) *deductionCoordinator {
return dc
}

func newRegistryDeductionCoordinator(superv *supervisor, registry Registry) deducer {
dc := &registryDeductionCoordinator{
suprvsr: superv,
rootxt: radix.New(),
deducext: pathDeducerTrie(),
registry: registry,
}
return dc
}

type registryDeductionCoordinator struct {
suprvsr *supervisor
mut sync.RWMutex
rootxt *radix.Tree
deducext *deducerTrie
registry Registry
}

func (dc *registryDeductionCoordinator) deduceRootPath(ctx context.Context, path string) (pathDeduction, error) {
if err := dc.suprvsr.ctx.Err(); err != nil {
return pathDeduction{}, err
}
// First, check the rootxt to see if there's a prefix match - if so, we
// can return that and move on.
dc.mut.RLock()
prefix, data, has := dc.rootxt.LongestPrefix(path)
dc.mut.RUnlock()
if has && isPathPrefixOrEqual(prefix, path) {
switch d := data.(type) {
case maybeSource:
return pathDeduction{root: prefix, mb: d}, nil
case *registryDeducer:
return d.deduce(ctx, path)
}
panic(fmt.Sprintf("unexpected %T in deductionCoordinator.rootxt: %v", data, data))
}

if _, mtch, has := dc.deducext.LongestPrefix(path); has {
root, err := mtch.deduceRoot(path)
if err == nil {
var u *url.URL
u, err = url.Parse(dc.registry.URL())
if err != nil {
return pathDeduction{}, err
}
mb := maybeRegistrySource{path: root, url: u, token: dc.registry.Token()}

dc.mut.Lock()
dc.rootxt.Insert(root, mb)
dc.mut.Unlock()
return pathDeduction{root: root, mb: mb}, nil
}
}

rd := &registryDeducer{
basePath: path,
registry: dc.registry,
suprvsr: dc.suprvsr,
// The registry deducer will call this func with a completed
// pathDeduction if it succeeds in finding one. We process it
// back through the action channel to ensure serialized
// access to the rootxt map.
returnFunc: func(pd pathDeduction) {
dc.mut.Lock()
dc.rootxt.Insert(pd.root, pd.mb)
dc.mut.Unlock()
},
}

// Save the rd in the rootxt so that calls checking on similar
// paths made while the request is in flight can be folded together.
dc.mut.Lock()
dc.rootxt.Insert(path, rd)
dc.mut.Unlock()

// Trigger the HTTP-backed deduction process for this requestor.
return rd.deduce(ctx, path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entails at least one HTTP request for each root path, including the known path case (which was removed above, compared with deductionCoordinator.deduceRootPath()). This will be a notable performance hit for the user vs. the current behavior.

More importantly, though, as long as we are guaranteeing that any proxy registry can access and passthrough-cache any upstream VCS source that a dep client would be able to access itself without an intermediate registry, then, then i see no compelling use case for having this extra request. In fact, all i see is a vector for confusion and errors, as we become reliant on registries to respect naming invariants that are presently universal, by virtue of being encoded in the tool. It seems like it should be possible - at least as a first pass approach - to just rely on the existing static deduction subsystem, then perform some structured transformations on the pathDeductions that they return.

Is there a crucial use case that's serviced by making this request to the registry?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Yes, it would be preferable to deduct the root project path without HTTP request. I added the known paths deduction to address this issue.

However, for imports that are not using well known URLs, passthrough upstream VCS source is not always possible. For instance, in a closed network environment that work exclusively through a private registry deducing the project root by querying the VCS repository for the goget metadata cannot work (leaving aside the fact that most of the benefits of having a local private registry are lost by bypassing and accessing the sources directly).
Therefore, there is a valid use case IMO for having the registry know about the project roots it hosts. Once, we allowed static deduction to kick in first I don't think the performance impact (a single HEAD call) would be an issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be preferable to deduct the root project path without HTTP request.

great 😄

However, for imports that are not using well known URLs,

for sure, i'm entirely in agreement that anything requiring normal go-get HTTP metadata deduction can be forwarded to the registry.

Once, we allowed static deduction to kick in first I don't think the performance impact (a single HEAD call) would be an issue.

agreed, that's no net increase in the number of HTTP requests, so no harm done.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Is it ready to be merged?

}

// deduceRootPath takes an import path and attempts to deduce various
// metadata about it - what type of source should handle it, and where its
// "root" is (for vcs repositories, the repository root).
Expand Down Expand Up @@ -697,6 +776,59 @@ func (dc *deductionCoordinator) deduceKnownPaths(path string) (pathDeduction, er
return pathDeduction{}, errNoKnownPathMatch
}

type registryDeducer struct {
once sync.Once
deduced pathDeduction
deduceErr error
basePath string
registry Registry
returnFunc func(pathDeduction)
suprvsr *supervisor
}

func (rd *registryDeducer) getProjectName(importPath string) (string, error) {
u, err := url.Parse(rd.registry.URL())
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, "api/v1/projects/", url.PathEscape(importPath))
req, err := http.NewRequest("HEAD", u.String(), nil)
if err != nil {
return "", err
}

req.Header.Set("Authorization", "Bearer "+rd.registry.Token())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
return "", errors.Errorf("%s %s", u, http.StatusText(resp.StatusCode))
}

return resp.Header.Get("X-Go-Project-Name"), nil
}

func (rd *registryDeducer) deduce(ctx context.Context, path string) (pathDeduction, error) {
rd.once.Do(func() {
projectName, err := rd.getProjectName(path)
if err != nil {
rd.deduceErr = err
return
}
var u *url.URL
u, err = url.Parse(rd.registry.URL())
if err != nil {
return
}

rd.deduced = pathDeduction{mb: maybeRegistrySource{path: projectName, url: u, token: rd.registry.Token()}, root: projectName}
rd.returnFunc(rd.deduced)
return
})
return rd.deduced, rd.deduceErr
}

type httpMetadataDeducer struct {
once sync.Once
deduced pathDeduction
Expand Down
Loading