Path prefix support for running registry somewhere other than root of server
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
This commit is contained in:
parent
47a8ad7a61
commit
1700f518cb
8 changed files with 163 additions and 9 deletions
|
@ -38,6 +38,8 @@ type Configuration struct {
|
||||||
// Addr specifies the bind address for the registry instance.
|
// Addr specifies the bind address for the registry instance.
|
||||||
Addr string `yaml:"addr,omitempty"`
|
Addr string `yaml:"addr,omitempty"`
|
||||||
|
|
||||||
|
Prefix string `yaml:"prefix,omitempty"`
|
||||||
|
|
||||||
// Secret specifies the secret key which HMAC tokens are created with.
|
// Secret specifies the secret key which HMAC tokens are created with.
|
||||||
Secret string `yaml:"secret,omitempty"`
|
Secret string `yaml:"secret,omitempty"`
|
||||||
|
|
||||||
|
|
|
@ -1410,13 +1410,18 @@ var errorDescriptors = []ErrorDescriptor{
|
||||||
|
|
||||||
var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
|
var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
|
||||||
var idToDescriptors map[string]ErrorDescriptor
|
var idToDescriptors map[string]ErrorDescriptor
|
||||||
|
var routeDescriptorsMap map[string]RouteDescriptor
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(errorDescriptors))
|
errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(errorDescriptors))
|
||||||
idToDescriptors = make(map[string]ErrorDescriptor, len(errorDescriptors))
|
idToDescriptors = make(map[string]ErrorDescriptor, len(errorDescriptors))
|
||||||
|
routeDescriptorsMap = make(map[string]RouteDescriptor, len(routeDescriptors))
|
||||||
|
|
||||||
for _, descriptor := range errorDescriptors {
|
for _, descriptor := range errorDescriptors {
|
||||||
errorCodeToDescriptors[descriptor.Code] = descriptor
|
errorCodeToDescriptors[descriptor.Code] = descriptor
|
||||||
idToDescriptors[descriptor.Value] = descriptor
|
idToDescriptors[descriptor.Value] = descriptor
|
||||||
}
|
}
|
||||||
|
for _, descriptor := range routeDescriptors {
|
||||||
|
routeDescriptorsMap[descriptor.Name] = descriptor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,23 @@ var allEndpoints = []string{
|
||||||
// methods. This can be used directly by both server implementations and
|
// methods. This can be used directly by both server implementations and
|
||||||
// clients.
|
// clients.
|
||||||
func Router() *mux.Router {
|
func Router() *mux.Router {
|
||||||
router := mux.NewRouter().
|
return RouterWithPrefix("")
|
||||||
StrictSlash(true)
|
}
|
||||||
|
|
||||||
|
// RouterWithPrefix builds a gorilla router with a configured prefix
|
||||||
|
// on all routes.
|
||||||
|
func RouterWithPrefix(prefix string) *mux.Router {
|
||||||
|
rootRouter := mux.NewRouter()
|
||||||
|
router := rootRouter
|
||||||
|
if prefix != "" {
|
||||||
|
router = router.PathPrefix(prefix).Subrouter()
|
||||||
|
}
|
||||||
|
|
||||||
|
router.StrictSlash(true)
|
||||||
|
|
||||||
for _, descriptor := range routeDescriptors {
|
for _, descriptor := range routeDescriptors {
|
||||||
router.Path(descriptor.Path).Name(descriptor.Name)
|
router.Path(descriptor.Path).Name(descriptor.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return router
|
return rootRouter
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -24,8 +25,16 @@ type routeTestCase struct {
|
||||||
//
|
//
|
||||||
// This may go away as the application structure comes together.
|
// This may go away as the application structure comes together.
|
||||||
func TestRouter(t *testing.T) {
|
func TestRouter(t *testing.T) {
|
||||||
|
baseTestRouter(t, "")
|
||||||
|
}
|
||||||
|
|
||||||
router := Router()
|
func TestRouterWithPrefix(t *testing.T) {
|
||||||
|
baseTestRouter(t, "/prefix/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseTestRouter(t *testing.T, prefix string) {
|
||||||
|
|
||||||
|
router := RouterWithPrefix(prefix)
|
||||||
|
|
||||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
testCase := routeTestCase{
|
testCase := routeTestCase{
|
||||||
|
@ -147,6 +156,8 @@ func TestRouter(t *testing.T) {
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
testcase.RequestURI = strings.TrimSuffix(prefix, "/") + testcase.RequestURI
|
||||||
|
|
||||||
// Register the endpoint
|
// Register the endpoint
|
||||||
route := router.GetRoute(testcase.RouteName)
|
route := router.GetRoute(testcase.RouteName)
|
||||||
if route == nil {
|
if route == nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package v2
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -64,11 +65,21 @@ func NewURLBuilderFromRequest(r *http.Request) *URLBuilder {
|
||||||
host = forwardedHost
|
host = forwardedHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basePath := routeDescriptorsMap[RouteNameBase].Path
|
||||||
|
|
||||||
|
requestPath := r.URL.Path
|
||||||
|
index := strings.Index(requestPath, basePath)
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
// N.B. index+1 is important because we want to include the trailing /
|
||||||
|
u.Path = requestPath[0 : index+1]
|
||||||
|
}
|
||||||
|
|
||||||
return NewURLBuilder(u)
|
return NewURLBuilder(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +182,10 @@ func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" {
|
||||||
|
routeURL.Path = routeURL.Path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
return cr.root.ResolveReference(routeURL), nil
|
return cr.root.ResolveReference(routeURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,35 @@ func TestURLBuilder(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestURLBuilderWithPrefix(t *testing.T) {
|
||||||
|
roots := []string{
|
||||||
|
"http://example.com/prefix/",
|
||||||
|
"https://example.com/prefix/",
|
||||||
|
"http://localhost:5000/prefix/",
|
||||||
|
"https://localhost:5443/prefix/",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, root := range roots {
|
||||||
|
urlBuilder, err := NewURLBuilderFromString(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range makeURLBuilderTestCases(urlBuilder) {
|
||||||
|
url, err := testCase.build()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedURL := root[0:len(root)-1] + testCase.expectedPath
|
||||||
|
|
||||||
|
if url != expectedURL {
|
||||||
|
t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type builderFromRequestTestCase struct {
|
type builderFromRequestTestCase struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
base string
|
base string
|
||||||
|
@ -153,3 +182,44 @@ func TestBuilderFromRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
||||||
|
u, err := url.Parse("http://example.com/prefix/v2/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardedProtoHeader := make(http.Header, 1)
|
||||||
|
forwardedProtoHeader.Set("X-Forwarded-Proto", "https")
|
||||||
|
|
||||||
|
testRequests := []struct {
|
||||||
|
request *http.Request
|
||||||
|
base string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
request: &http.Request{URL: u, Host: u.Host},
|
||||||
|
base: "http://example.com/prefix/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||||
|
base: "https://example.com/prefix/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tr := range testRequests {
|
||||||
|
builder := NewURLBuilderFromRequest(tr.request)
|
||||||
|
|
||||||
|
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||||
|
url, err := testCase.build()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedURL := tr.base[0:len(tr.base)-1] + testCase.expectedPath
|
||||||
|
|
||||||
|
if url != expectedURL {
|
||||||
|
t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
|
@ -57,6 +58,40 @@ func TestCheckAPI(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestURLPrefix(t *testing.T) {
|
||||||
|
config := configuration.Configuration{
|
||||||
|
Storage: configuration.Storage{
|
||||||
|
"inmemory": configuration.Parameters{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.HTTP.Prefix = "/test/"
|
||||||
|
|
||||||
|
env := newTestEnvWithConfig(t, &config)
|
||||||
|
|
||||||
|
baseURL, err := env.builder.BuildBaseURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error building base url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, _ := url.Parse(baseURL)
|
||||||
|
if !strings.HasPrefix(parsed.Path, config.HTTP.Prefix) {
|
||||||
|
t.Fatalf("Prefix %v not included in test url %v", config.HTTP.Prefix, baseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error issuing request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "issuing api base check", resp, http.StatusOK)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Content-Type": []string{"application/json; charset=utf-8"},
|
||||||
|
"Content-Length": []string{"2"},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TestLayerAPI conducts a full of the of the layer api.
|
// TestLayerAPI conducts a full of the of the layer api.
|
||||||
func TestLayerAPI(t *testing.T) {
|
func TestLayerAPI(t *testing.T) {
|
||||||
// TODO(stevvooe): This test code is complete junk but it should cover the
|
// TODO(stevvooe): This test code is complete junk but it should cover the
|
||||||
|
@ -356,16 +391,21 @@ type testEnv struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestEnv(t *testing.T) *testEnv {
|
func newTestEnv(t *testing.T) *testEnv {
|
||||||
ctx := context.Background()
|
|
||||||
config := configuration.Configuration{
|
config := configuration.Configuration{
|
||||||
Storage: configuration.Storage{
|
Storage: configuration.Storage{
|
||||||
"inmemory": configuration.Parameters{},
|
"inmemory": configuration.Parameters{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app := NewApp(ctx, config)
|
return newTestEnvWithConfig(t, &config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
app := NewApp(ctx, *config)
|
||||||
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
||||||
builder, err := v2.NewURLBuilderFromString(server.URL)
|
builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating url builder: %v", err)
|
t.Fatalf("error creating url builder: %v", err)
|
||||||
|
@ -379,7 +419,7 @@ func newTestEnv(t *testing.T) *testEnv {
|
||||||
return &testEnv{
|
return &testEnv{
|
||||||
pk: pk,
|
pk: pk,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
config: config,
|
config: *config,
|
||||||
app: app,
|
app: app,
|
||||||
server: server,
|
server: server,
|
||||||
builder: builder,
|
builder: builder,
|
||||||
|
|
|
@ -64,7 +64,7 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
Config: configuration,
|
Config: configuration,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
InstanceID: uuid.New(),
|
InstanceID: uuid.New(),
|
||||||
router: v2.Router(),
|
router: v2.RouterWithPrefix(configuration.HTTP.Prefix),
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "app.id"))
|
app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "app.id"))
|
||||||
|
|
Loading…
Reference in a new issue