Path prefix support for running registry somewhere other than root of server
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)master
							parent
							
								
									00ce453315
								
							
						
					
					
						commit
						871cf9dd01
					
				| 
						 | 
				
			
			@ -1410,13 +1410,18 @@ var errorDescriptors = []ErrorDescriptor{
 | 
			
		|||
 | 
			
		||||
var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
 | 
			
		||||
var idToDescriptors map[string]ErrorDescriptor
 | 
			
		||||
var routeDescriptorsMap map[string]RouteDescriptor
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(errorDescriptors))
 | 
			
		||||
	idToDescriptors = make(map[string]ErrorDescriptor, len(errorDescriptors))
 | 
			
		||||
	routeDescriptorsMap = make(map[string]RouteDescriptor, len(routeDescriptors))
 | 
			
		||||
 | 
			
		||||
	for _, descriptor := range errorDescriptors {
 | 
			
		||||
		errorCodeToDescriptors[descriptor.Code] = 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
 | 
			
		||||
// clients.
 | 
			
		||||
func Router() *mux.Router {
 | 
			
		||||
	router := mux.NewRouter().
 | 
			
		||||
		StrictSlash(true)
 | 
			
		||||
	return RouterWithPrefix("")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
		router.Path(descriptor.Path).Name(descriptor.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return router
 | 
			
		||||
	return rootRouter
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,8 +25,16 @@ type routeTestCase struct {
 | 
			
		|||
//
 | 
			
		||||
// This may go away as the application structure comes together.
 | 
			
		||||
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) {
 | 
			
		||||
		testCase := routeTestCase{
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +156,8 @@ func TestRouter(t *testing.T) {
 | 
			
		|||
			StatusCode: http.StatusNotFound,
 | 
			
		||||
		},
 | 
			
		||||
	} {
 | 
			
		||||
		testcase.RequestURI = strings.TrimSuffix(prefix, "/") + testcase.RequestURI
 | 
			
		||||
 | 
			
		||||
		// Register the endpoint
 | 
			
		||||
		route := router.GetRoute(testcase.RouteName)
 | 
			
		||||
		if route == nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ package v2
 | 
			
		|||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/distribution/digest"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
| 
						 | 
				
			
			@ -64,11 +65,21 @@ func NewURLBuilderFromRequest(r *http.Request) *URLBuilder {
 | 
			
		|||
		host = forwardedHost
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	basePath := routeDescriptorsMap[RouteNameBase].Path
 | 
			
		||||
 | 
			
		||||
	requestPath := r.URL.Path
 | 
			
		||||
	index := strings.Index(requestPath, basePath)
 | 
			
		||||
 | 
			
		||||
	u := &url.URL{
 | 
			
		||||
		Scheme: scheme,
 | 
			
		||||
		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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -171,6 +182,10 @@ func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" {
 | 
			
		||||
		routeURL.Path = routeURL.Path[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	request *http.Request
 | 
			
		||||
	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"
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"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.
 | 
			
		||||
func TestLayerAPI(t *testing.T) {
 | 
			
		||||
	// 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 {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	config := configuration.Configuration{
 | 
			
		||||
		Storage: configuration.Storage{
 | 
			
		||||
			"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))
 | 
			
		||||
	builder, err := v2.NewURLBuilderFromString(server.URL)
 | 
			
		||||
	builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error creating url builder: %v", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -379,7 +419,7 @@ func newTestEnv(t *testing.T) *testEnv {
 | 
			
		|||
	return &testEnv{
 | 
			
		||||
		pk:      pk,
 | 
			
		||||
		ctx:     ctx,
 | 
			
		||||
		config:  config,
 | 
			
		||||
		config:  *config,
 | 
			
		||||
		app:     app,
 | 
			
		||||
		server:  server,
 | 
			
		||||
		builder: builder,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,7 +64,7 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
 | 
			
		|||
		Config:     configuration,
 | 
			
		||||
		Context:    ctx,
 | 
			
		||||
		InstanceID: uuid.New(),
 | 
			
		||||
		router:     v2.Router(),
 | 
			
		||||
		router:     v2.RouterWithPrefix(configuration.HTTP.Prefix),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "app.id"))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue