forked from TrueCloudLab/distribution
Merge pull request #1491 from RichardScothern/relative-url
Return relative URLs
This commit is contained in:
commit
9e690c7fa2
10 changed files with 237 additions and 98 deletions
|
@ -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
|
||||||
|
|
|
@ -68,6 +68,7 @@ var configStruct = Configuration{
|
||||||
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"`
|
||||||
|
RelativeURLs bool `yaml:"relativeurls,omitempty"`
|
||||||
TLS struct {
|
TLS struct {
|
||||||
Certificate string `yaml:"certificate,omitempty"`
|
Certificate string `yaml:"certificate,omitempty"`
|
||||||
Key string `yaml:"key,omitempty"`
|
Key string `yaml:"key,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",
|
||||||
|
|
|
@ -19,31 +19,33 @@ import (
|
||||||
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,8 +92,9 @@ func TestURLBuilder(t *testing.T) {
|
||||||
"https://localhost:5443",
|
"https://localhost:5443",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doTest := func(relative bool) {
|
||||||
for _, root := range roots {
|
for _, root := range roots {
|
||||||
urlBuilder, err := NewURLBuilderFromString(root)
|
urlBuilder, err := NewURLBuilderFromString(root, relative)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -103,8 +104,10 @@ func TestURLBuilder(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)
|
||||||
}
|
}
|
||||||
|
expectedURL := testCase.expectedPath
|
||||||
expectedURL := root + 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)
|
||||||
|
@ -112,6 +115,9 @@ func TestURLBuilder(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
doTest(true)
|
||||||
|
doTest(false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestURLBuilderWithPrefix(t *testing.T) {
|
func TestURLBuilderWithPrefix(t *testing.T) {
|
||||||
roots := []string{
|
roots := []string{
|
||||||
|
@ -121,8 +127,9 @@ func TestURLBuilderWithPrefix(t *testing.T) {
|
||||||
"https://localhost:5443/prefix/",
|
"https://localhost:5443/prefix/",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doTest := func(relative bool) {
|
||||||
for _, root := range roots {
|
for _, root := range roots {
|
||||||
urlBuilder, err := NewURLBuilderFromString(root)
|
urlBuilder, err := NewURLBuilderFromString(root, relative)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -133,14 +140,19 @@ func TestURLBuilderWithPrefix(t *testing.T) {
|
||||||
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedURL := root[0:len(root)-1] + testCase.expectedPath
|
expectedURL := testCase.expectedPath
|
||||||
|
if !relative {
|
||||||
|
expectedURL = root[0:len(root)-1] + 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)
|
||||||
|
}
|
||||||
|
|
||||||
type builderFromRequestTestCase struct {
|
type builderFromRequestTestCase struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
|
@ -197,13 +209,13 @@ 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 {
|
} else {
|
||||||
builder = NewURLBuilderFromRequest(tr.request)
|
builder = NewURLBuilderFromRequest(tr.request, relative)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range makeURLBuilderTestCases(builder) {
|
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||||
|
@ -215,14 +227,20 @@ func TestBuilderFromRequest(t *testing.T) {
|
||||||
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 + testCase.expectedPath
|
expectedURL = testCase.expectedPath
|
||||||
|
if !relative {
|
||||||
|
expectedURL = tr.base + 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() + testCase.expectedPath
|
expectedURL = testCase.expectedPath
|
||||||
|
if !relative {
|
||||||
|
expectedURL = urlBase.String() + expectedURL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if buildURL != expectedURL {
|
if buildURL != expectedURL {
|
||||||
|
@ -231,6 +249,9 @@ func TestBuilderFromRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
doTest(true)
|
||||||
|
doTest(false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
||||||
u, err := url.Parse("http://example.com/prefix/v2/")
|
u, err := url.Parse("http://example.com/prefix/v2/")
|
||||||
|
@ -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 a new issue