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
							
								
									47a8ad7a61
								
							
						
					
					
						commit
						1700f518cb
					
				|  | @ -38,6 +38,8 @@ type Configuration struct { | |||
| 		// Addr specifies the bind address for the registry instance.
 | ||||
| 		Addr string `yaml:"addr,omitempty"` | ||||
| 
 | ||||
| 		Prefix string `yaml:"prefix,omitempty"` | ||||
| 
 | ||||
| 		// Secret specifies the secret key which HMAC tokens are created with.
 | ||||
| 		Secret string `yaml:"secret,omitempty"` | ||||
| 
 | ||||
|  |  | |||
|  | @ -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