Enable URLs returned from the registry to be configured as relative.
Signed-off-by: Richard Scothern <richard.scothern@gmail.com>master
							parent
							
								
									609aa7cc53
								
							
						
					
					
						commit
						bc9c820e4b
					
				|  | @ -73,6 +73,10 @@ type Configuration struct { | ||||||
| 		// 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"` | ||||||
| 
 | 
 | ||||||
|  | 		// RelativeURLs specifies that relative URLs should be returned in
 | ||||||
|  | 		// Location headers
 | ||||||
|  | 		RelativeURLs bool `yaml:"relativeurls,omitempty"` | ||||||
|  | 
 | ||||||
| 		// TLS instructs the http server to listen with a TLS configuration.
 | 		// TLS instructs the http server to listen with a TLS configuration.
 | ||||||
| 		// This only support simple tls configuration with a cert and key.
 | 		// This only support simple tls configuration with a cert and key.
 | ||||||
| 		// Mostly, this is useful for testing situations or simple deployments
 | 		// Mostly, this is useful for testing situations or simple deployments
 | ||||||
|  |  | ||||||
|  | @ -63,12 +63,13 @@ var configStruct = Configuration{ | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	HTTP: struct { | 	HTTP: struct { | ||||||
| 		Addr   string `yaml:"addr,omitempty"` | 		Addr         string `yaml:"addr,omitempty"` | ||||||
| 		Net    string `yaml:"net,omitempty"` | 		Net          string `yaml:"net,omitempty"` | ||||||
| 		Host   string `yaml:"host,omitempty"` | 		Host         string `yaml:"host,omitempty"` | ||||||
| 		Prefix string `yaml:"prefix,omitempty"` | 		Prefix       string `yaml:"prefix,omitempty"` | ||||||
| 		Secret string `yaml:"secret,omitempty"` | 		Secret       string `yaml:"secret,omitempty"` | ||||||
| 		TLS    struct { | 		RelativeURLs bool   `yaml:"relativeurls,omitempty"` | ||||||
|  | 		TLS          struct { | ||||||
| 			Certificate string   `yaml:"certificate,omitempty"` | 			Certificate string   `yaml:"certificate,omitempty"` | ||||||
| 			Key         string   `yaml:"key,omitempty"` | 			Key         string   `yaml:"key,omitempty"` | ||||||
| 			ClientCAs   []string `yaml:"clientcas,omitempty"` | 			ClientCAs   []string `yaml:"clientcas,omitempty"` | ||||||
|  |  | ||||||
|  | @ -179,6 +179,7 @@ information about each option that appears later in this page. | ||||||
|       prefix: /my/nested/registry/ |       prefix: /my/nested/registry/ | ||||||
|       host: https://myregistryaddress.org:5000 |       host: https://myregistryaddress.org:5000 | ||||||
|       secret: asecretforlocaldevelopment |       secret: asecretforlocaldevelopment | ||||||
|  |       relativeurls: false | ||||||
|       tls: |       tls: | ||||||
|         certificate: /path/to/x509/public |         certificate: /path/to/x509/public | ||||||
|         key: /path/to/x509/private |         key: /path/to/x509/private | ||||||
|  | @ -902,6 +903,7 @@ configuration may contain both. | ||||||
|       prefix: /my/nested/registry/ |       prefix: /my/nested/registry/ | ||||||
|       host: https://myregistryaddress.org:5000 |       host: https://myregistryaddress.org:5000 | ||||||
|       secret: asecretforlocaldevelopment |       secret: asecretforlocaldevelopment | ||||||
|  |       relativeurls: false | ||||||
|       tls: |       tls: | ||||||
|         certificate: /path/to/x509/public |         certificate: /path/to/x509/public | ||||||
|         key: /path/to/x509/private |         key: /path/to/x509/private | ||||||
|  | @ -989,6 +991,19 @@ generate a secret at launch. | ||||||
| ensure the secret is the same for all registries.</b> | ensure the secret is the same for all registries.</b> | ||||||
|     </td> |     </td> | ||||||
|   </tr> |   </tr> | ||||||
|  |   <tr> | ||||||
|  |     <td> | ||||||
|  |       <code>relativeurls</code> | ||||||
|  |     </td> | ||||||
|  |     <td> | ||||||
|  |       no | ||||||
|  |     </td> | ||||||
|  |     <td> | ||||||
|  |        Specifies that the registry should return relative URLs in Location headers. | ||||||
|  |        The client is responsible for resolving the correct URL.  This option is not | ||||||
|  |        compatible with Docker 1.7 and earlier. | ||||||
|  |     </td> | ||||||
|  |   </tr> | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ var ( | ||||||
| 		Addr:       "remote.test", | 		Addr:       "remote.test", | ||||||
| 		InstanceID: uuid.Generate().String(), | 		InstanceID: uuid.Generate().String(), | ||||||
| 	} | 	} | ||||||
| 	ub = mustUB(v2.NewURLBuilderFromString("http://test.example.com/")) | 	ub = mustUB(v2.NewURLBuilderFromString("http://test.example.com/", false)) | ||||||
| 
 | 
 | ||||||
| 	actor = ActorRecord{ | 	actor = ActorRecord{ | ||||||
| 		Name: "test", | 		Name: "test", | ||||||
|  |  | ||||||
|  | @ -17,33 +17,35 @@ import ( | ||||||
| // under "/foo/v2/...". Most application will only provide a schema, host and
 | // under "/foo/v2/...". Most application will only provide a schema, host and
 | ||||||
| // port, such as "https://localhost:5000/".
 | // port, such as "https://localhost:5000/".
 | ||||||
| type URLBuilder struct { | type URLBuilder struct { | ||||||
| 	root   *url.URL // url root (ie http://localhost/)
 | 	root     *url.URL // url root (ie http://localhost/)
 | ||||||
| 	router *mux.Router | 	router   *mux.Router | ||||||
|  | 	relative bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewURLBuilder creates a URLBuilder with provided root url object.
 | // NewURLBuilder creates a URLBuilder with provided root url object.
 | ||||||
| func NewURLBuilder(root *url.URL) *URLBuilder { | func NewURLBuilder(root *url.URL, relative bool) *URLBuilder { | ||||||
| 	return &URLBuilder{ | 	return &URLBuilder{ | ||||||
| 		root:   root, | 		root:     root, | ||||||
| 		router: Router(), | 		router:   Router(), | ||||||
|  | 		relative: relative, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewURLBuilderFromString workes identically to NewURLBuilder except it takes
 | // NewURLBuilderFromString workes identically to NewURLBuilder except it takes
 | ||||||
| // a string argument for the root, returning an error if it is not a valid
 | // a string argument for the root, returning an error if it is not a valid
 | ||||||
| // url.
 | // url.
 | ||||||
| func NewURLBuilderFromString(root string) (*URLBuilder, error) { | func NewURLBuilderFromString(root string, relative bool) (*URLBuilder, error) { | ||||||
| 	u, err := url.Parse(root) | 	u, err := url.Parse(root) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return NewURLBuilder(u), nil | 	return NewURLBuilder(u, relative), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewURLBuilderFromRequest uses information from an *http.Request to
 | // NewURLBuilderFromRequest uses information from an *http.Request to
 | ||||||
| // construct the root url.
 | // construct the root url.
 | ||||||
| func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { | func NewURLBuilderFromRequest(r *http.Request, relative bool) *URLBuilder { | ||||||
| 	var scheme string | 	var scheme string | ||||||
| 
 | 
 | ||||||
| 	forwardedProto := r.Header.Get("X-Forwarded-Proto") | 	forwardedProto := r.Header.Get("X-Forwarded-Proto") | ||||||
|  | @ -85,7 +87,7 @@ func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { | ||||||
| 		u.Path = requestPath[0 : index+1] | 		u.Path = requestPath[0 : index+1] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return NewURLBuilder(u) | 	return NewURLBuilder(u, relative) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // BuildBaseURL constructs a base url for the API, typically just "/v2/".
 | // BuildBaseURL constructs a base url for the API, typically just "/v2/".
 | ||||||
|  | @ -194,12 +196,13 @@ func (ub *URLBuilder) cloneRoute(name string) clonedRoute { | ||||||
| 	*route = *ub.router.GetRoute(name) // clone the route
 | 	*route = *ub.router.GetRoute(name) // clone the route
 | ||||||
| 	*root = *ub.root | 	*root = *ub.root | ||||||
| 
 | 
 | ||||||
| 	return clonedRoute{Route: route, root: root} | 	return clonedRoute{Route: route, root: root, relative: ub.relative} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type clonedRoute struct { | type clonedRoute struct { | ||||||
| 	*mux.Route | 	*mux.Route | ||||||
| 	root *url.URL | 	root     *url.URL | ||||||
|  | 	relative bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { | func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { | ||||||
|  | @ -208,6 +211,10 @@ func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if cr.relative { | ||||||
|  | 		return routeURL, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" { | 	if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" { | ||||||
| 		routeURL.Path = routeURL.Path[1:] | 		routeURL.Path = routeURL.Path[1:] | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -92,25 +92,31 @@ func TestURLBuilder(t *testing.T) { | ||||||
| 		"https://localhost:5443", | 		"https://localhost:5443", | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, root := range roots { | 	doTest := func(relative bool) { | ||||||
| 		urlBuilder, err := NewURLBuilderFromString(root) | 		for _, root := range roots { | ||||||
| 		if err != nil { | 			urlBuilder, err := NewURLBuilderFromString(root, relative) | ||||||
| 			t.Fatalf("unexpected error creating urlbuilder: %v", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, testCase := range makeURLBuilderTestCases(urlBuilder) { |  | ||||||
| 			url, err := testCase.build() |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("%s: error building url: %v", testCase.description, err) | 				t.Fatalf("unexpected error creating urlbuilder: %v", err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			expectedURL := root + testCase.expectedPath | 			for _, testCase := range makeURLBuilderTestCases(urlBuilder) { | ||||||
|  | 				url, err := testCase.build() | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Fatalf("%s: error building url: %v", testCase.description, err) | ||||||
|  | 				} | ||||||
|  | 				expectedURL := testCase.expectedPath | ||||||
|  | 				if !relative { | ||||||
|  | 					expectedURL = root + expectedURL | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 			if url != expectedURL { | 				if url != expectedURL { | ||||||
| 				t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) | 					t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	doTest(true) | ||||||
|  | 	doTest(false) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestURLBuilderWithPrefix(t *testing.T) { | func TestURLBuilderWithPrefix(t *testing.T) { | ||||||
|  | @ -121,25 +127,31 @@ func TestURLBuilderWithPrefix(t *testing.T) { | ||||||
| 		"https://localhost:5443/prefix/", | 		"https://localhost:5443/prefix/", | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, root := range roots { | 	doTest := func(relative bool) { | ||||||
| 		urlBuilder, err := NewURLBuilderFromString(root) | 		for _, root := range roots { | ||||||
| 		if err != nil { | 			urlBuilder, err := NewURLBuilderFromString(root, relative) | ||||||
| 			t.Fatalf("unexpected error creating urlbuilder: %v", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, testCase := range makeURLBuilderTestCases(urlBuilder) { |  | ||||||
| 			url, err := testCase.build() |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("%s: error building url: %v", testCase.description, err) | 				t.Fatalf("unexpected error creating urlbuilder: %v", err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			expectedURL := root[0:len(root)-1] + testCase.expectedPath | 			for _, testCase := range makeURLBuilderTestCases(urlBuilder) { | ||||||
|  | 				url, err := testCase.build() | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Fatalf("%s: error building url: %v", testCase.description, err) | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 			if url != expectedURL { | 				expectedURL := testCase.expectedPath | ||||||
| 				t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) | 				if !relative { | ||||||
|  | 					expectedURL = root[0:len(root)-1] + expectedURL | ||||||
|  | 				} | ||||||
|  | 				if url != expectedURL { | ||||||
|  | 					t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	doTest(true) | ||||||
|  | 	doTest(false) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type builderFromRequestTestCase struct { | type builderFromRequestTestCase struct { | ||||||
|  | @ -197,39 +209,48 @@ func TestBuilderFromRequest(t *testing.T) { | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 	doTest := func(relative bool) { | ||||||
| 	for _, tr := range testRequests { | 		for _, tr := range testRequests { | ||||||
| 		var builder *URLBuilder | 			var builder *URLBuilder | ||||||
| 		if tr.configHost.Scheme != "" && tr.configHost.Host != "" { | 			if tr.configHost.Scheme != "" && tr.configHost.Host != "" { | ||||||
| 			builder = NewURLBuilder(&tr.configHost) | 				builder = NewURLBuilder(&tr.configHost, relative) | ||||||
| 		} else { |  | ||||||
| 			builder = NewURLBuilderFromRequest(tr.request) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, testCase := range makeURLBuilderTestCases(builder) { |  | ||||||
| 			buildURL, err := testCase.build() |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Fatalf("%s: error building url: %v", testCase.description, err) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			var expectedURL string |  | ||||||
| 			proto, ok := tr.request.Header["X-Forwarded-Proto"] |  | ||||||
| 			if !ok { |  | ||||||
| 				expectedURL = tr.base + testCase.expectedPath |  | ||||||
| 			} else { | 			} else { | ||||||
| 				urlBase, err := url.Parse(tr.base) | 				builder = NewURLBuilderFromRequest(tr.request, relative) | ||||||
| 				if err != nil { |  | ||||||
| 					t.Fatal(err) |  | ||||||
| 				} |  | ||||||
| 				urlBase.Scheme = proto[0] |  | ||||||
| 				expectedURL = urlBase.String() + testCase.expectedPath |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if buildURL != expectedURL { | 			for _, testCase := range makeURLBuilderTestCases(builder) { | ||||||
| 				t.Fatalf("%s: %q != %q", testCase.description, buildURL, expectedURL) | 				buildURL, err := testCase.build() | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Fatalf("%s: error building url: %v", testCase.description, err) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var expectedURL string | ||||||
|  | 				proto, ok := tr.request.Header["X-Forwarded-Proto"] | ||||||
|  | 				if !ok { | ||||||
|  | 					expectedURL = testCase.expectedPath | ||||||
|  | 					if !relative { | ||||||
|  | 						expectedURL = tr.base + expectedURL | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					urlBase, err := url.Parse(tr.base) | ||||||
|  | 					if err != nil { | ||||||
|  | 						t.Fatal(err) | ||||||
|  | 					} | ||||||
|  | 					urlBase.Scheme = proto[0] | ||||||
|  | 					expectedURL = testCase.expectedPath | ||||||
|  | 					if !relative { | ||||||
|  | 						expectedURL = urlBase.String() + expectedURL | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if buildURL != expectedURL { | ||||||
|  | 					t.Fatalf("%s: %q != %q", testCase.description, buildURL, expectedURL) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	doTest(true) | ||||||
|  | 	doTest(false) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestBuilderFromRequestWithPrefix(t *testing.T) { | func TestBuilderFromRequestWithPrefix(t *testing.T) { | ||||||
|  | @ -270,12 +291,13 @@ func TestBuilderFromRequestWithPrefix(t *testing.T) { | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var relative bool | ||||||
| 	for _, tr := range testRequests { | 	for _, tr := range testRequests { | ||||||
| 		var builder *URLBuilder | 		var builder *URLBuilder | ||||||
| 		if tr.configHost.Scheme != "" && tr.configHost.Host != "" { | 		if tr.configHost.Scheme != "" && tr.configHost.Host != "" { | ||||||
| 			builder = NewURLBuilder(&tr.configHost) | 			builder = NewURLBuilder(&tr.configHost, false) | ||||||
| 		} else { | 		} else { | ||||||
| 			builder = NewURLBuilderFromRequest(tr.request) | 			builder = NewURLBuilderFromRequest(tr.request, false) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for _, testCase := range makeURLBuilderTestCases(builder) { | 		for _, testCase := range makeURLBuilderTestCases(builder) { | ||||||
|  | @ -283,17 +305,25 @@ func TestBuilderFromRequestWithPrefix(t *testing.T) { | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("%s: error building url: %v", testCase.description, err) | 				t.Fatalf("%s: error building url: %v", testCase.description, err) | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
| 			var expectedURL string | 			var expectedURL string | ||||||
| 			proto, ok := tr.request.Header["X-Forwarded-Proto"] | 			proto, ok := tr.request.Header["X-Forwarded-Proto"] | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				expectedURL = tr.base[0:len(tr.base)-1] + testCase.expectedPath | 				expectedURL = testCase.expectedPath | ||||||
|  | 				if !relative { | ||||||
|  | 					expectedURL = tr.base[0:len(tr.base)-1] + expectedURL | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				urlBase, err := url.Parse(tr.base) | 				urlBase, err := url.Parse(tr.base) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					t.Fatal(err) | 					t.Fatal(err) | ||||||
| 				} | 				} | ||||||
| 				urlBase.Scheme = proto[0] | 				urlBase.Scheme = proto[0] | ||||||
| 				expectedURL = urlBase.String()[0:len(urlBase.String())-1] + testCase.expectedPath | 				expectedURL = testCase.expectedPath | ||||||
|  | 				if !relative { | ||||||
|  | 					expectedURL = urlBase.String()[0:len(urlBase.String())-1] + expectedURL | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if buildURL != expectedURL { | 			if buildURL != expectedURL { | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ func checkHTTPRedirect(req *http.Request, via []*http.Request) error { | ||||||
| 
 | 
 | ||||||
| // NewRegistry creates a registry namespace which can be used to get a listing of repositories
 | // NewRegistry creates a registry namespace which can be used to get a listing of repositories
 | ||||||
| func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTripper) (Registry, error) { | func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTripper) (Registry, error) { | ||||||
| 	ub, err := v2.NewURLBuilderFromString(baseURL) | 	ub, err := v2.NewURLBuilderFromString(baseURL, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -133,7 +133,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri | ||||||
| 
 | 
 | ||||||
| // NewRepository creates a new Repository for the given repository name and base URL.
 | // NewRepository creates a new Repository for the given repository name and base URL.
 | ||||||
| func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) { | func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) { | ||||||
| 	ub, err := v2.NewURLBuilderFromString(baseURL) | 	ub, err := v2.NewURLBuilderFromString(baseURL, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -43,7 +43,6 @@ var headerConfig = http.Header{ | ||||||
| // 200 OK response.
 | // 200 OK response.
 | ||||||
| func TestCheckAPI(t *testing.T) { | func TestCheckAPI(t *testing.T) { | ||||||
| 	env := newTestEnv(t, false) | 	env := newTestEnv(t, false) | ||||||
| 
 |  | ||||||
| 	baseURL, err := env.builder.BuildBaseURL() | 	baseURL, err := env.builder.BuildBaseURL() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error building base url: %v", err) | 		t.Fatalf("unexpected error building base url: %v", err) | ||||||
|  | @ -294,6 +293,79 @@ func TestBlobDelete(t *testing.T) { | ||||||
| 	testBlobDelete(t, env, args) | 	testBlobDelete(t, env, args) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestRelativeURL(t *testing.T) { | ||||||
|  | 	config := configuration.Configuration{ | ||||||
|  | 		Storage: configuration.Storage{ | ||||||
|  | 			"inmemory": configuration.Parameters{}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	config.HTTP.Headers = headerConfig | ||||||
|  | 	config.HTTP.RelativeURLs = false | ||||||
|  | 	env := newTestEnvWithConfig(t, &config) | ||||||
|  | 	ref, _ := reference.WithName("foo/bar") | ||||||
|  | 	uploadURLBaseAbs, _ := startPushLayer(t, env, ref) | ||||||
|  | 
 | ||||||
|  | 	u, err := url.Parse(uploadURLBaseAbs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !u.IsAbs() { | ||||||
|  | 		t.Fatal("Relative URL returned from blob upload chunk with non-relative configuration") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	args := makeBlobArgs(t) | ||||||
|  | 	resp, err := doPushLayer(t, env.builder, ref, args.layerDigest, uploadURLBaseAbs, args.layerFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error doing layer push relative url: %v", err) | ||||||
|  | 	} | ||||||
|  | 	checkResponse(t, "relativeurl blob upload", resp, http.StatusCreated) | ||||||
|  | 	u, err = url.Parse(resp.Header.Get("Location")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !u.IsAbs() { | ||||||
|  | 		t.Fatal("Relative URL returned from blob upload with non-relative configuration") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	config.HTTP.RelativeURLs = true | ||||||
|  | 	args = makeBlobArgs(t) | ||||||
|  | 	uploadURLBaseRelative, _ := startPushLayer(t, env, ref) | ||||||
|  | 	u, err = url.Parse(uploadURLBaseRelative) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if u.IsAbs() { | ||||||
|  | 		t.Fatal("Absolute URL returned from blob upload chunk with relative configuration") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Start a new upload in absolute mode to get a valid base URL
 | ||||||
|  | 	config.HTTP.RelativeURLs = false | ||||||
|  | 	uploadURLBaseAbs, _ = startPushLayer(t, env, ref) | ||||||
|  | 	u, err = url.Parse(uploadURLBaseAbs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !u.IsAbs() { | ||||||
|  | 		t.Fatal("Relative URL returned from blob upload chunk with non-relative configuration") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Complete upload with relative URLs enabled to ensure the final location is relative
 | ||||||
|  | 	config.HTTP.RelativeURLs = true | ||||||
|  | 	resp, err = doPushLayer(t, env.builder, ref, args.layerDigest, uploadURLBaseAbs, args.layerFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error doing layer push relative url: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	checkResponse(t, "relativeurl blob upload", resp, http.StatusCreated) | ||||||
|  | 	u, err = url.Parse(resp.Header.Get("Location")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if u.IsAbs() { | ||||||
|  | 		t.Fatal("Relative URL returned from blob upload with non-relative configuration") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestBlobDeleteDisabled(t *testing.T) { | func TestBlobDeleteDisabled(t *testing.T) { | ||||||
| 	deleteEnabled := false | 	deleteEnabled := false | ||||||
| 	env := newTestEnv(t, deleteEnabled) | 	env := newTestEnv(t, deleteEnabled) | ||||||
|  | @ -349,7 +421,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 
 | 
 | ||||||
| 	// ------------------------------------------
 | 	// ------------------------------------------
 | ||||||
| 	// Start an upload, check the status then cancel
 | 	// Start an upload, check the status then cancel
 | ||||||
| 	uploadURLBase, uploadUUID := startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID := startPushLayer(t, env, imageName) | ||||||
| 
 | 
 | ||||||
| 	// A status check should work
 | 	// A status check should work
 | ||||||
| 	resp, err = http.Get(uploadURLBase) | 	resp, err = http.Get(uploadURLBase) | ||||||
|  | @ -384,7 +456,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 
 | 
 | ||||||
| 	// -----------------------------------------
 | 	// -----------------------------------------
 | ||||||
| 	// Do layer push with an empty body and different digest
 | 	// Do layer push with an empty body and different digest
 | ||||||
| 	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID = startPushLayer(t, env, imageName) | ||||||
| 	resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{})) | 	resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{})) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error doing bad layer push: %v", err) | 		t.Fatalf("unexpected error doing bad layer push: %v", err) | ||||||
|  | @ -400,7 +472,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 		t.Fatalf("unexpected error digesting empty buffer: %v", err) | 		t.Fatalf("unexpected error digesting empty buffer: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID = startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{})) | 	pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{})) | ||||||
| 
 | 
 | ||||||
| 	// -----------------------------------------
 | 	// -----------------------------------------
 | ||||||
|  | @ -413,7 +485,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 		t.Fatalf("unexpected error digesting empty tar: %v", err) | 		t.Fatalf("unexpected error digesting empty tar: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID = startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar)) | 	pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar)) | ||||||
| 
 | 
 | ||||||
| 	// ------------------------------------------
 | 	// ------------------------------------------
 | ||||||
|  | @ -421,7 +493,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 	layerLength, _ := layerFile.Seek(0, os.SEEK_END) | 	layerLength, _ := layerFile.Seek(0, os.SEEK_END) | ||||||
| 	layerFile.Seek(0, os.SEEK_SET) | 	layerFile.Seek(0, os.SEEK_SET) | ||||||
| 
 | 
 | ||||||
| 	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID = startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | ||||||
| 
 | 
 | ||||||
| 	// ------------------------------------------
 | 	// ------------------------------------------
 | ||||||
|  | @ -435,7 +507,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { | ||||||
| 	canonicalDigest := canonicalDigester.Digest() | 	canonicalDigest := canonicalDigester.Digest() | ||||||
| 
 | 
 | ||||||
| 	layerFile.Seek(0, 0) | 	layerFile.Seek(0, 0) | ||||||
| 	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName) | 	uploadURLBase, uploadUUID = startPushLayer(t, env, imageName) | ||||||
| 	uploadURLBase, dgst := pushChunk(t, env.builder, imageName, uploadURLBase, layerFile, layerLength) | 	uploadURLBase, dgst := pushChunk(t, env.builder, imageName, uploadURLBase, layerFile, layerLength) | ||||||
| 	finishUpload(t, env.builder, imageName, uploadURLBase, dgst) | 	finishUpload(t, env.builder, imageName, uploadURLBase, dgst) | ||||||
| 
 | 
 | ||||||
|  | @ -585,7 +657,7 @@ func testBlobDelete(t *testing.T, env *testEnv, args blobArgs) { | ||||||
| 	// Reupload previously deleted blob
 | 	// Reupload previously deleted blob
 | ||||||
| 	layerFile.Seek(0, os.SEEK_SET) | 	layerFile.Seek(0, os.SEEK_SET) | ||||||
| 
 | 
 | ||||||
| 	uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 	uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | ||||||
| 
 | 
 | ||||||
| 	layerFile.Seek(0, os.SEEK_SET) | 	layerFile.Seek(0, os.SEEK_SET) | ||||||
|  | @ -625,7 +697,7 @@ func TestDeleteDisabled(t *testing.T) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Error building blob URL") | 		t.Fatalf("Error building blob URL") | ||||||
| 	} | 	} | ||||||
| 	uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 	uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | ||||||
| 
 | 
 | ||||||
| 	resp, err := httpDelete(layerURL) | 	resp, err := httpDelete(layerURL) | ||||||
|  | @ -651,7 +723,7 @@ func TestDeleteReadOnly(t *testing.T) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Error building blob URL") | 		t.Fatalf("Error building blob URL") | ||||||
| 	} | 	} | ||||||
| 	uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 	uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | 	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile) | ||||||
| 
 | 
 | ||||||
| 	env.app.readOnly = true | 	env.app.readOnly = true | ||||||
|  | @ -871,7 +943,7 @@ func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Name | ||||||
| 		expectedLayers[dgst] = rs | 		expectedLayers[dgst] = rs | ||||||
| 		unsignedManifest.FSLayers[i].BlobSum = dgst | 		unsignedManifest.FSLayers[i].BlobSum = dgst | ||||||
| 
 | 
 | ||||||
| 		uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 		uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) | 		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1177,7 +1249,7 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name | ||||||
| 	}`) | 	}`) | ||||||
| 	sampleConfigDigest := digest.FromBytes(sampleConfig) | 	sampleConfigDigest := digest.FromBytes(sampleConfig) | ||||||
| 
 | 
 | ||||||
| 	uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 	uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 	pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig)) | 	pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig)) | ||||||
| 	manifest.Config.Digest = sampleConfigDigest | 	manifest.Config.Digest = sampleConfigDigest | ||||||
| 	manifest.Config.Size = int64(len(sampleConfig)) | 	manifest.Config.Size = int64(len(sampleConfig)) | ||||||
|  | @ -1210,7 +1282,7 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name | ||||||
| 		expectedLayers[dgst] = rs | 		expectedLayers[dgst] = rs | ||||||
| 		manifest.Layers[i].Digest = dgst | 		manifest.Layers[i].Digest = dgst | ||||||
| 
 | 
 | ||||||
| 		uploadURLBase, _ := startPushLayer(t, env.builder, imageName) | 		uploadURLBase, _ := startPushLayer(t, env, imageName) | ||||||
| 		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) | 		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1842,7 +1914,7 @@ func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *te | ||||||
| 
 | 
 | ||||||
| 	app := NewApp(ctx, config) | 	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 + config.HTTP.Prefix) | 	builder, err := v2.NewURLBuilderFromString(server.URL+config.HTTP.Prefix, false) | ||||||
| 
 | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("error creating url builder: %v", err) | 		t.Fatalf("error creating url builder: %v", err) | ||||||
|  | @ -1904,21 +1976,33 @@ func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *htt | ||||||
| 	return resp | 	return resp | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func startPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named) (location string, uuid string) { | func startPushLayer(t *testing.T, env *testEnv, name reference.Named) (location string, uuid string) { | ||||||
| 	layerUploadURL, err := ub.BuildBlobUploadURL(name) | 	layerUploadURL, err := env.builder.BuildBlobUploadURL(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error building layer upload url: %v", err) | 		t.Fatalf("unexpected error building layer upload url: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	u, err := url.Parse(layerUploadURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("error parsing layer upload URL: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	base, err := url.Parse(env.server.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("error parsing server URL: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	layerUploadURL = base.ResolveReference(u).String() | ||||||
| 	resp, err := http.Post(layerUploadURL, "", nil) | 	resp, err := http.Post(layerUploadURL, "", nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error starting layer push: %v", err) | 		t.Fatalf("unexpected error starting layer push: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
| 
 | 
 | ||||||
| 	checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name.String()), resp, http.StatusAccepted) | 	checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name.String()), resp, http.StatusAccepted) | ||||||
| 
 | 
 | ||||||
| 	u, err := url.Parse(resp.Header.Get("Location")) | 	u, err = url.Parse(resp.Header.Get("Location")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("error parsing location header: %v", err) | 		t.Fatalf("error parsing location header: %v", err) | ||||||
| 	} | 	} | ||||||
|  | @ -1943,7 +2027,6 @@ func doPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst dig | ||||||
| 
 | 
 | ||||||
| 	u.RawQuery = url.Values{ | 	u.RawQuery = url.Values{ | ||||||
| 		"_state": u.Query()["_state"], | 		"_state": u.Query()["_state"], | ||||||
| 
 |  | ||||||
| 		"digest": []string{dgst.String()}, | 		"digest": []string{dgst.String()}, | ||||||
| 	}.Encode() | 	}.Encode() | ||||||
| 
 | 
 | ||||||
|  | @ -2211,8 +2294,7 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string) | ||||||
| 
 | 
 | ||||||
| 		expectedLayers[dgst] = rs | 		expectedLayers[dgst] = rs | ||||||
| 		unsignedManifest.FSLayers[i].BlobSum = dgst | 		unsignedManifest.FSLayers[i].BlobSum = dgst | ||||||
| 
 | 		uploadURLBase, _ := startPushLayer(t, env, imageNameRef) | ||||||
| 		uploadURLBase, _ := startPushLayer(t, env.builder, imageNameRef) |  | ||||||
| 		pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs) | 		pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -721,9 +721,9 @@ func (app *App) context(w http.ResponseWriter, r *http.Request) *Context { | ||||||
| 		// A "host" item in the configuration takes precedence over
 | 		// A "host" item in the configuration takes precedence over
 | ||||||
| 		// X-Forwarded-Proto and X-Forwarded-Host headers, and the
 | 		// X-Forwarded-Proto and X-Forwarded-Host headers, and the
 | ||||||
| 		// hostname in the request.
 | 		// hostname in the request.
 | ||||||
| 		context.urlBuilder = v2.NewURLBuilder(&app.httpHost) | 		context.urlBuilder = v2.NewURLBuilder(&app.httpHost, false) | ||||||
| 	} else { | 	} else { | ||||||
| 		context.urlBuilder = v2.NewURLBuilderFromRequest(r) | 		context.urlBuilder = v2.NewURLBuilderFromRequest(r, app.Config.HTTP.RelativeURLs) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return context | 	return context | ||||||
|  |  | ||||||
|  | @ -160,7 +160,7 @@ func TestNewApp(t *testing.T) { | ||||||
| 	app := NewApp(ctx, &config) | 	app := NewApp(ctx, &config) | ||||||
| 
 | 
 | ||||||
| 	server := httptest.NewServer(app) | 	server := httptest.NewServer(app) | ||||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL) | 	builder, err := v2.NewURLBuilderFromString(server.URL, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("error creating urlbuilder: %v", err) | 		t.Fatalf("error creating urlbuilder: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue