Merge pull request #1491 from RichardScothern/relative-url

Return relative URLs
This commit is contained in:
Stephen Day 2016-03-23 16:19:39 -07:00
commit 9e690c7fa2
10 changed files with 237 additions and 98 deletions

View file

@ -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

View file

@ -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"`

View file

@ -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>

View file

@ -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",

View file

@ -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:]
} }

View file

@ -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 {

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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

View file

@ -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)
} }