Merge pull request #202 from endophage/master
registry/handlers: support prefixed registry appmaster
						commit
						2aa230468a
					
				| 
						 | 
					@ -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 New Issue