mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Vendor updated distribution for resumable downloads
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
69c381c8d1
commit
4d437a29d2
15 changed files with 256 additions and 98 deletions
|
@ -250,7 +250,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
|
||||||
p.confirmedV2 = true
|
p.confirmedV2 = true
|
||||||
|
|
||||||
logrus.Debugf("Pulling ref from V2 registry: %s", ref.String())
|
logrus.Debugf("Pulling ref from V2 registry: %s", ref.String())
|
||||||
progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Name())
|
progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Named().Name())
|
||||||
|
|
||||||
var (
|
var (
|
||||||
imageID image.ID
|
imageID image.ID
|
||||||
|
|
|
@ -166,7 +166,11 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, ima
|
||||||
if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
|
if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
|
||||||
logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
|
logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
|
||||||
|
|
||||||
builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, p.repo.Name(), ref.Tag(), img.RawJSON())
|
manifestRef, err := distreference.WithTag(p.repo.Named(), ref.Tag())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, manifestRef, img.RawJSON())
|
||||||
manifest, err = manifestFromBuilder(ctx, builder, descriptors)
|
manifest, err = manifestFromBuilder(ctx, builder, descriptors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -219,7 +223,7 @@ type v2PushDescriptor struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *v2PushDescriptor) Key() string {
|
func (pd *v2PushDescriptor) Key() string {
|
||||||
return "v2push:" + pd.repo.Name() + " " + pd.layer.DiffID().String()
|
return "v2push:" + pd.repo.Named().Name() + " " + pd.layer.DiffID().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *v2PushDescriptor) ID() string {
|
func (pd *v2PushDescriptor) ID() string {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
|
distreference "github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/client"
|
"github.com/docker/distribution/registry/client"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
|
@ -154,7 +155,12 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
|
||||||
}
|
}
|
||||||
tr := transport.NewTransport(base, modifiers...)
|
tr := transport.NewTransport(base, modifiers...)
|
||||||
|
|
||||||
repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr)
|
repoNameRef, err := distreference.ParseNamed(repoName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, foundVersion, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL, tr)
|
||||||
return repo, foundVersion, err
|
return repo, foundVersion, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ clone git github.com/boltdb/bolt v1.1.0
|
||||||
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
|
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
|
||||||
|
|
||||||
# get graph and distribution packages
|
# get graph and distribution packages
|
||||||
clone git github.com/docker/distribution c301f8ab27f4913c968b8d73a38e5dda79b9d3d7
|
clone git github.com/docker/distribution ab9b433fcaf7c8319562a8b80f2720f5faca712f
|
||||||
clone git github.com/vbatts/tar-split v0.9.11
|
clone git github.com/vbatts/tar-split v0.9.11
|
||||||
|
|
||||||
# get desired notary commit, might also need to be updated in Dockerfile
|
# get desired notary commit, might also need to be updated in Dockerfile
|
||||||
|
|
|
@ -2,6 +2,7 @@ Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.gith
|
||||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
||||||
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
||||||
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
||||||
|
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
|
||||||
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
||||||
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
|
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
|
||||||
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
|
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
|
||||||
|
@ -11,4 +12,4 @@ Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com>
|
||||||
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
|
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
|
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
|
||||||
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
|
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
|
||||||
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
|
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
|
||||||
|
|
|
@ -21,6 +21,7 @@ Ben Firshman <ben@firshman.co.uk>
|
||||||
bin liu <liubin0329@gmail.com>
|
bin liu <liubin0329@gmail.com>
|
||||||
Brian Bland <brian.bland@docker.com>
|
Brian Bland <brian.bland@docker.com>
|
||||||
burnettk <burnettk@gmail.com>
|
burnettk <burnettk@gmail.com>
|
||||||
|
Carson A <ca@carsonoid.net>
|
||||||
Chris Dillon <squarism@gmail.com>
|
Chris Dillon <squarism@gmail.com>
|
||||||
Daisuke Fujita <dtanshi45@gmail.com>
|
Daisuke Fujita <dtanshi45@gmail.com>
|
||||||
Darren Shepherd <darren@rancher.com>
|
Darren Shepherd <darren@rancher.com>
|
||||||
|
@ -33,11 +34,13 @@ davidli <wenquan.li@hp.com>
|
||||||
Dejan Golja <dejan@golja.org>
|
Dejan Golja <dejan@golja.org>
|
||||||
Derek McGowan <derek@mcgstyle.net>
|
Derek McGowan <derek@mcgstyle.net>
|
||||||
Diogo Mónica <diogo.monica@gmail.com>
|
Diogo Mónica <diogo.monica@gmail.com>
|
||||||
|
DJ Enriquez <dj.enriquez@infospace.com>
|
||||||
Donald Huang <don.hcd@gmail.com>
|
Donald Huang <don.hcd@gmail.com>
|
||||||
Doug Davis <dug@us.ibm.com>
|
Doug Davis <dug@us.ibm.com>
|
||||||
farmerworking <farmerworking@gmail.com>
|
farmerworking <farmerworking@gmail.com>
|
||||||
Florentin Raud <florentin.raud@gmail.com>
|
Florentin Raud <florentin.raud@gmail.com>
|
||||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||||
|
gabriell nascimento <gabriell@bluesoft.com.br>
|
||||||
harche <p.harshal@gmail.com>
|
harche <p.harshal@gmail.com>
|
||||||
Henri Gomez <henri.gomez@gmail.com>
|
Henri Gomez <henri.gomez@gmail.com>
|
||||||
Hu Keping <hukeping@huawei.com>
|
Hu Keping <hukeping@huawei.com>
|
||||||
|
@ -55,7 +58,9 @@ Josh Hawn <josh.hawn@docker.com>
|
||||||
Julien Fernandez <julien.fernandez@gmail.com>
|
Julien Fernandez <julien.fernandez@gmail.com>
|
||||||
Kelsey Hightower <kelsey.hightower@gmail.com>
|
Kelsey Hightower <kelsey.hightower@gmail.com>
|
||||||
Kenneth Lim <kennethlimcp@gmail.com>
|
Kenneth Lim <kennethlimcp@gmail.com>
|
||||||
|
Kenny Leung <kleung@google.com>
|
||||||
Li Yi <denverdino@gmail.com>
|
Li Yi <denverdino@gmail.com>
|
||||||
|
Liu Hua <sdu.liu@huawei.com>
|
||||||
Louis Kottmann <louis.kottmann@gmail.com>
|
Louis Kottmann <louis.kottmann@gmail.com>
|
||||||
Luke Carpenter <x@rubynerd.net>
|
Luke Carpenter <x@rubynerd.net>
|
||||||
Mary Anthony <mary@docker.com>
|
Mary Anthony <mary@docker.com>
|
||||||
|
@ -76,7 +81,9 @@ Olivier Jacques <olivier.jacques@hp.com>
|
||||||
Patrick Devine <patrick.devine@docker.com>
|
Patrick Devine <patrick.devine@docker.com>
|
||||||
Philip Misiowiec <philip@atlashealth.com>
|
Philip Misiowiec <philip@atlashealth.com>
|
||||||
Richard Scothern <richard.scothern@docker.com>
|
Richard Scothern <richard.scothern@docker.com>
|
||||||
|
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||||
Rusty Conover <rusty@luckydinosaur.com>
|
Rusty Conover <rusty@luckydinosaur.com>
|
||||||
|
Sean Boran <Boran@users.noreply.github.com>
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
Sharif Nassar <sharif@mrwacky.com>
|
Sharif Nassar <sharif@mrwacky.com>
|
||||||
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
|
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
|
||||||
|
@ -93,11 +100,13 @@ Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
Tibor Vass <teabee89@gmail.com>
|
Tibor Vass <teabee89@gmail.com>
|
||||||
Tonis Tiigi <tonistiigi@gmail.com>
|
Tonis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Trevor Pounds <trevor.pounds@gmail.com>
|
||||||
Troels Thomsen <troels@thomsen.io>
|
Troels Thomsen <troels@thomsen.io>
|
||||||
Vincent Batts <vbatts@redhat.com>
|
Vincent Batts <vbatts@redhat.com>
|
||||||
Vincent Demeester <vincent@sbr.pm>
|
Vincent Demeester <vincent@sbr.pm>
|
||||||
Vincent Giersch <vincent.giersch@ovh.net>
|
Vincent Giersch <vincent.giersch@ovh.net>
|
||||||
W. Trevor King <wking@tremily.us>
|
W. Trevor King <wking@tremily.us>
|
||||||
|
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
|
||||||
xg.song <xg.song@venusource.com>
|
xg.song <xg.song@venusource.com>
|
||||||
xiekeyang <xiekeyang@huawei.com>
|
xiekeyang <xiekeyang@huawei.com>
|
||||||
Yann ROBERT <yann.robert@anantaplex.fr>
|
Yann ROBERT <yann.robert@anantaplex.fr>
|
||||||
|
|
|
@ -32,6 +32,11 @@
|
||||||
Email = "aaron.lehmann@docker.com"
|
Email = "aaron.lehmann@docker.com"
|
||||||
GitHub = "aaronlehmann"
|
GitHub = "aaronlehmann"
|
||||||
|
|
||||||
|
[people.brianbland]
|
||||||
|
Name = "Brian Bland"
|
||||||
|
Email = "brian.bland@docker.com"
|
||||||
|
GitHub = "BrianBland"
|
||||||
|
|
||||||
[people.dmcgowan]
|
[people.dmcgowan]
|
||||||
Name = "Derek McGowan"
|
Name = "Derek McGowan"
|
||||||
Email = "derek@mcgstyle.net"
|
Email = "derek@mcgstyle.net"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
|
@ -39,10 +40,8 @@ type configManifestBuilder struct {
|
||||||
// configJSON is configuration supplied when the ManifestBuilder was
|
// configJSON is configuration supplied when the ManifestBuilder was
|
||||||
// created.
|
// created.
|
||||||
configJSON []byte
|
configJSON []byte
|
||||||
// name is the name provided to NewConfigManifestBuilder
|
// ref contains the name and optional tag provided to NewConfigManifestBuilder.
|
||||||
name string
|
ref reference.Named
|
||||||
// tag is the tag provided to NewConfigManifestBuilder
|
|
||||||
tag string
|
|
||||||
// descriptors is the set of descriptors referencing the layers.
|
// descriptors is the set of descriptors referencing the layers.
|
||||||
descriptors []distribution.Descriptor
|
descriptors []distribution.Descriptor
|
||||||
// emptyTarDigest is set to a valid digest if an empty tar has been
|
// emptyTarDigest is set to a valid digest if an empty tar has been
|
||||||
|
@ -54,13 +53,12 @@ type configManifestBuilder struct {
|
||||||
// schema version from an image configuration and a set of descriptors.
|
// schema version from an image configuration and a set of descriptors.
|
||||||
// It takes a BlobService so that it can add an empty tar to the blob store
|
// It takes a BlobService so that it can add an empty tar to the blob store
|
||||||
// if the resulting manifest needs empty layers.
|
// if the resulting manifest needs empty layers.
|
||||||
func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, name, tag string, configJSON []byte) distribution.ManifestBuilder {
|
func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder {
|
||||||
return &configManifestBuilder{
|
return &configManifestBuilder{
|
||||||
bs: bs,
|
bs: bs,
|
||||||
pk: pk,
|
pk: pk,
|
||||||
configJSON: configJSON,
|
configJSON: configJSON,
|
||||||
name: name,
|
ref: ref,
|
||||||
tag: tag,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,12 +188,17 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani
|
||||||
|
|
||||||
history[0].V1Compatibility = string(transformedConfig)
|
history[0].V1Compatibility = string(transformedConfig)
|
||||||
|
|
||||||
|
tag := ""
|
||||||
|
if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
|
||||||
|
tag = tagged.Tag()
|
||||||
|
}
|
||||||
|
|
||||||
mfst := Manifest{
|
mfst := Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: manifest.Versioned{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 1,
|
||||||
},
|
},
|
||||||
Name: mb.name,
|
Name: mb.ref.Name(),
|
||||||
Tag: mb.tag,
|
Tag: tag,
|
||||||
Architecture: img.Architecture,
|
Architecture: img.Architecture,
|
||||||
FSLayers: fsLayerList,
|
FSLayers: fsLayerList,
|
||||||
History: history,
|
History: history,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,13 +21,18 @@ type referenceManifestBuilder struct {
|
||||||
|
|
||||||
// NewReferenceManifestBuilder is used to build new manifests for the current
|
// NewReferenceManifestBuilder is used to build new manifests for the current
|
||||||
// schema version using schema1 dependencies.
|
// schema version using schema1 dependencies.
|
||||||
func NewReferenceManifestBuilder(pk libtrust.PrivateKey, name, tag, architecture string) distribution.ManifestBuilder {
|
func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder {
|
||||||
|
tag := ""
|
||||||
|
if tagged, isTagged := ref.(reference.Tagged); isTagged {
|
||||||
|
tag = tagged.Tag()
|
||||||
|
}
|
||||||
|
|
||||||
return &referenceManifestBuilder{
|
return &referenceManifestBuilder{
|
||||||
Manifest: Manifest{
|
Manifest: Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: manifest.Versioned{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 1,
|
||||||
},
|
},
|
||||||
Name: name,
|
Name: ref.Name(),
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Architecture: architecture,
|
Architecture: architecture,
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,7 +81,7 @@ type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
|
||||||
|
|
||||||
var mappings = make(map[string]UnmarshalFunc, 0)
|
var mappings = make(map[string]UnmarshalFunc, 0)
|
||||||
|
|
||||||
// UnmarshalManifest looks up manifest unmarshall functions based on
|
// UnmarshalManifest looks up manifest unmarshal functions based on
|
||||||
// MediaType
|
// MediaType
|
||||||
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
|
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
|
||||||
// Need to look up by the actual media type, not the raw contents of
|
// Need to look up by the actual media type, not the raw contents of
|
||||||
|
|
|
@ -2,6 +2,7 @@ package distribution
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scope defines the set of items that match a namespace.
|
// Scope defines the set of items that match a namespace.
|
||||||
|
@ -32,7 +33,7 @@ type Namespace interface {
|
||||||
// Repository should return a reference to the named repository. The
|
// Repository should return a reference to the named repository. The
|
||||||
// registry may or may not have the repository but should always return a
|
// registry may or may not have the repository but should always return a
|
||||||
// reference.
|
// reference.
|
||||||
Repository(ctx context.Context, name string) (Repository, error)
|
Repository(ctx context.Context, name reference.Named) (Repository, error)
|
||||||
|
|
||||||
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
|
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
|
||||||
// up to the size of 'repos' and returns the value 'n' for the number of entries
|
// up to the size of 'repos' and returns the value 'n' for the number of entries
|
||||||
|
@ -48,8 +49,8 @@ type ManifestServiceOption interface {
|
||||||
|
|
||||||
// Repository is a named collection of manifests and layers.
|
// Repository is a named collection of manifests and layers.
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
// Name returns the name of the repository.
|
// Named returns the name of the repository.
|
||||||
Name() string
|
Named() reference.Named
|
||||||
|
|
||||||
// Manifests returns a reference to this repository's manifest service.
|
// Manifests returns a reference to this repository's manifest service.
|
||||||
// with the supplied options applied.
|
// with the supplied options applied.
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,10 +113,10 @@ func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildTagsURL constructs a url to list the tags in the named repository.
|
// BuildTagsURL constructs a url to list the tags in the named repository.
|
||||||
func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameTags)
|
route := ub.cloneRoute(RouteNameTags)
|
||||||
|
|
||||||
tagsURL, err := route.URL("name", name)
|
tagsURL, err := route.URL("name", name.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -126,10 +126,18 @@ func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
||||||
|
|
||||||
// BuildManifestURL constructs a url for the manifest identified by name and
|
// BuildManifestURL constructs a url for the manifest identified by name and
|
||||||
// reference. The argument reference may be either a tag or digest.
|
// reference. The argument reference may be either a tag or digest.
|
||||||
func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
|
func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameManifest)
|
route := ub.cloneRoute(RouteNameManifest)
|
||||||
|
|
||||||
manifestURL, err := route.URL("name", name, "reference", reference)
|
tagOrDigest := ""
|
||||||
|
switch v := ref.(type) {
|
||||||
|
case reference.Tagged:
|
||||||
|
tagOrDigest = v.Tag()
|
||||||
|
case reference.Digested:
|
||||||
|
tagOrDigest = v.Digest().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -138,10 +146,10 @@ func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildBlobURL constructs the url for the blob identified by name and dgst.
|
// BuildBlobURL constructs the url for the blob identified by name and dgst.
|
||||||
func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) {
|
func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameBlob)
|
route := ub.cloneRoute(RouteNameBlob)
|
||||||
|
|
||||||
layerURL, err := route.URL("name", name, "digest", dgst.String())
|
layerURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -151,10 +159,10 @@ func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, err
|
||||||
|
|
||||||
// BuildBlobUploadURL constructs a url to begin a blob upload in the
|
// BuildBlobUploadURL constructs a url to begin a blob upload in the
|
||||||
// repository identified by name.
|
// repository identified by name.
|
||||||
func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) {
|
func (ub *URLBuilder) BuildBlobUploadURL(name reference.Named, values ...url.Values) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameBlobUpload)
|
route := ub.cloneRoute(RouteNameBlobUpload)
|
||||||
|
|
||||||
uploadURL, err := route.URL("name", name)
|
uploadURL, err := route.URL("name", name.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -166,10 +174,10 @@ func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (str
|
||||||
// including any url values. This should generally not be used by clients, as
|
// including any url values. This should generally not be used by clients, as
|
||||||
// this url is provided by server implementations during the blob upload
|
// this url is provided by server implementations during the blob upload
|
||||||
// process.
|
// process.
|
||||||
func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) {
|
func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameBlobUploadChunk)
|
route := ub.cloneRoute(RouteNameBlobUploadChunk)
|
||||||
|
|
||||||
uploadURL, err := route.URL("name", name, "uuid", uuid)
|
uploadURL, err := route.URL("name", name.Name(), "uuid", uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,9 +285,9 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon
|
||||||
}
|
}
|
||||||
|
|
||||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||||
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
|
||||||
// The default/minimum lifetime.
|
// The default/minimum lifetime.
|
||||||
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||||
|
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tr.IssuedAt.IsZero() {
|
if tr.IssuedAt.IsZero() {
|
||||||
|
|
|
@ -27,6 +27,26 @@ type Registry interface {
|
||||||
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkHTTPRedirect is a callback that can manipulate redirected HTTP
|
||||||
|
// requests. It is used to preserve Accept and Range headers.
|
||||||
|
func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(via) > 0 {
|
||||||
|
for headerName, headerVals := range via[0].Header {
|
||||||
|
if headerName == "Accept" || headerName == "Range" {
|
||||||
|
for _, val := range headerVals {
|
||||||
|
req.Header.Add(headerName, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -35,8 +55,9 @@ func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTrippe
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Timeout: 1 * time.Minute,
|
Timeout: 1 * time.Minute,
|
||||||
|
CheckRedirect: checkHTTPRedirect,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ®istry{
|
return ®istry{
|
||||||
|
@ -98,18 +119,15 @@ 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, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
||||||
if _, err := reference.ParseNamed(name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ub, err := v2.NewURLBuilderFromString(baseURL)
|
ub, err := v2.NewURLBuilderFromString(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
|
CheckRedirect: checkHTTPRedirect,
|
||||||
// TODO(dmcgowan): create cookie jar
|
// TODO(dmcgowan): create cookie jar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,21 +143,21 @@ type repository struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
ub *v2.URLBuilder
|
ub *v2.URLBuilder
|
||||||
context context.Context
|
context context.Context
|
||||||
name string
|
name reference.Named
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repository) Name() string {
|
func (r *repository) Named() reference.Named {
|
||||||
return r.name
|
return r.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||||
statter := &blobStatter{
|
statter := &blobStatter{
|
||||||
name: r.Name(),
|
name: r.name,
|
||||||
ub: r.ub,
|
ub: r.ub,
|
||||||
client: r.client,
|
client: r.client,
|
||||||
}
|
}
|
||||||
return &blobs{
|
return &blobs{
|
||||||
name: r.Name(),
|
name: r.name,
|
||||||
ub: r.ub,
|
ub: r.ub,
|
||||||
client: r.client,
|
client: r.client,
|
||||||
statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter),
|
statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter),
|
||||||
|
@ -149,7 +167,7 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||||
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
||||||
// todo(richardscothern): options should be sent over the wire
|
// todo(richardscothern): options should be sent over the wire
|
||||||
return &manifests{
|
return &manifests{
|
||||||
name: r.Name(),
|
name: r.name,
|
||||||
ub: r.ub,
|
ub: r.ub,
|
||||||
client: r.client,
|
client: r.client,
|
||||||
etags: make(map[string]string),
|
etags: make(map[string]string),
|
||||||
|
@ -161,7 +179,7 @@ func (r *repository) Tags(ctx context.Context) distribution.TagService {
|
||||||
client: r.client,
|
client: r.client,
|
||||||
ub: r.ub,
|
ub: r.ub,
|
||||||
context: r.context,
|
context: r.context,
|
||||||
name: r.Name(),
|
name: r.Named(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +188,7 @@ type tags struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
ub *v2.URLBuilder
|
ub *v2.URLBuilder
|
||||||
context context.Context
|
context context.Context
|
||||||
name string
|
name reference.Named
|
||||||
}
|
}
|
||||||
|
|
||||||
// All returns all tags
|
// All returns all tags
|
||||||
|
@ -253,7 +271,11 @@ func descriptorFromResponse(response *http.Response) (distribution.Descriptor, e
|
||||||
// to construct a descriptor for the tag. If the registry doesn't support HEADing
|
// to construct a descriptor for the tag. If the registry doesn't support HEADing
|
||||||
// a manifest, fallback to GET.
|
// a manifest, fallback to GET.
|
||||||
func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
||||||
u, err := t.ub.BuildManifestURL(t.name, tag)
|
ref, err := reference.WithTag(t.name, tag)
|
||||||
|
if err != nil {
|
||||||
|
return distribution.Descriptor{}, err
|
||||||
|
}
|
||||||
|
u, err := t.ub.BuildManifestURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
@ -293,14 +315,18 @@ func (t *tags) Untag(ctx context.Context, tag string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type manifests struct {
|
type manifests struct {
|
||||||
name string
|
name reference.Named
|
||||||
ub *v2.URLBuilder
|
ub *v2.URLBuilder
|
||||||
client *http.Client
|
client *http.Client
|
||||||
etags map[string]string
|
etags map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
||||||
u, err := ms.ub.BuildManifestURL(ms.name, dgst.String())
|
ref, err := reference.WithDigest(ms.name, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
u, err := ms.ub.BuildManifestURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -337,11 +363,19 @@ func (o etagOption) Apply(ms distribution.ManifestService) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
||||||
|
var (
|
||||||
|
digestOrTag string
|
||||||
|
ref reference.Named
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
var tag string
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if opt, ok := option.(withTagOption); ok {
|
if opt, ok := option.(withTagOption); ok {
|
||||||
tag = opt.tag
|
digestOrTag = opt.tag
|
||||||
|
ref, err = reference.WithTag(ms.name, opt.tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err := option.Apply(ms)
|
err := option.Apply(ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -350,14 +384,15 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ref string
|
if digestOrTag == "" {
|
||||||
if tag != "" {
|
digestOrTag = dgst.String()
|
||||||
ref = tag
|
ref, err = reference.WithDigest(ms.name, dgst)
|
||||||
} else {
|
if err != nil {
|
||||||
ref = dgst.String()
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := ms.ub.BuildManifestURL(ms.name, ref)
|
u, err := ms.ub.BuildManifestURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -371,8 +406,8 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis
|
||||||
req.Header.Add("Accept", t)
|
req.Header.Add("Accept", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := ms.etags[ref]; ok {
|
if _, ok := ms.etags[digestOrTag]; ok {
|
||||||
req.Header.Set("If-None-Match", ms.etags[ref])
|
req.Header.Set("If-None-Match", ms.etags[digestOrTag])
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := ms.client.Do(req)
|
resp, err := ms.client.Do(req)
|
||||||
|
@ -414,13 +449,19 @@ func (o withTagOption) Apply(m distribution.ManifestService) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
|
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
|
||||||
// tag name in order to build the correct upload URL. This state is written and read under a lock.
|
// tag name in order to build the correct upload URL.
|
||||||
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
||||||
var tag string
|
ref := ms.name
|
||||||
|
var tagged bool
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if opt, ok := option.(withTagOption); ok {
|
if opt, ok := option.(withTagOption); ok {
|
||||||
tag = opt.tag
|
var err error
|
||||||
|
ref, err = reference.WithTag(ref, opt.tag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tagged = true
|
||||||
} else {
|
} else {
|
||||||
err := option.Apply(ms)
|
err := option.Apply(ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -428,13 +469,24 @@ func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options .
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mediaType, p, err := m.Payload()
|
||||||
manifestURL, err := ms.ub.BuildManifestURL(ms.name, tag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaType, p, err := m.Payload()
|
if !tagged {
|
||||||
|
// generate a canonical digest and Put by digest
|
||||||
|
_, d, err := distribution.UnmarshalManifest(mediaType, p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ref, err = reference.WithDigest(ref, d.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestURL, err := ms.ub.BuildManifestURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -466,7 +518,11 @@ func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options .
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||||
u, err := ms.ub.BuildManifestURL(ms.name, dgst.String())
|
ref, err := reference.WithDigest(ms.name, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u, err := ms.ub.BuildManifestURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -493,7 +549,7 @@ func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
type blobs struct {
|
type blobs struct {
|
||||||
name string
|
name reference.Named
|
||||||
ub *v2.URLBuilder
|
ub *v2.URLBuilder
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
|
||||||
|
@ -531,7 +587,11 @@ func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
|
func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
|
||||||
blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
ref, err := reference.WithDigest(bs.name, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -666,13 +726,17 @@ func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type blobStatter struct {
|
type blobStatter struct {
|
||||||
name string
|
name reference.Named
|
||||||
ub *v2.URLBuilder
|
ub *v2.URLBuilder
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||||
u, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
ref, err := reference.WithDigest(bs.name, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return distribution.Descriptor{}, err
|
||||||
|
}
|
||||||
|
u, err := bs.ub.BuildBlobURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
@ -720,7 +784,11 @@ func buildCatalogValues(maxEntries int, last string) url.Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||||
blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
ref, err := reference.WithDigest(bs.name, dgst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
package transport
|
package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`)
|
||||||
|
|
||||||
|
// ErrWrongCodeForByteRange is returned if the client sends a request
|
||||||
|
// with a Range header but the server returns a 2xx or 3xx code other
|
||||||
|
// than 206 Partial Content.
|
||||||
|
ErrWrongCodeForByteRange = errors.New("expected HTTP 206 from byte range request")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadSeekCloser combines io.ReadSeeker with io.Closer.
|
// ReadSeekCloser combines io.ReadSeeker with io.Closer.
|
||||||
|
@ -40,8 +50,6 @@ type httpReadSeeker struct {
|
||||||
|
|
||||||
// rc is the remote read closer.
|
// rc is the remote read closer.
|
||||||
rc io.ReadCloser
|
rc io.ReadCloser
|
||||||
// brd is a buffer for internal buffered io.
|
|
||||||
brd *bufio.Reader
|
|
||||||
// readerOffset tracks the offset as of the last read.
|
// readerOffset tracks the offset as of the last read.
|
||||||
readerOffset int64
|
readerOffset int64
|
||||||
// seekOffset allows Seek to override the offset. Seek changes
|
// seekOffset allows Seek to override the offset. Seek changes
|
||||||
|
@ -79,11 +87,6 @@ func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
||||||
hrs.seekOffset += int64(n)
|
hrs.seekOffset += int64(n)
|
||||||
hrs.readerOffset += int64(n)
|
hrs.readerOffset += int64(n)
|
||||||
|
|
||||||
// Simulate io.EOF error if we reach filesize.
|
|
||||||
if err == nil && hrs.size >= 0 && hrs.readerOffset >= hrs.size {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +95,18 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||||
return 0, hrs.err
|
return 0, hrs.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastReaderOffset := hrs.readerOffset
|
||||||
|
|
||||||
|
if whence == os.SEEK_SET && hrs.rc == nil {
|
||||||
|
// If no request has been made yet, and we are seeking to an
|
||||||
|
// absolute position, set the read offset as well to avoid an
|
||||||
|
// unnecessary request.
|
||||||
|
hrs.readerOffset = offset
|
||||||
|
}
|
||||||
|
|
||||||
_, err := hrs.reader()
|
_, err := hrs.reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
hrs.readerOffset = lastReaderOffset
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,14 +114,14 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
|
||||||
switch whence {
|
switch whence {
|
||||||
case os.SEEK_CUR:
|
case os.SEEK_CUR:
|
||||||
newOffset += int64(offset)
|
newOffset += offset
|
||||||
case os.SEEK_END:
|
case os.SEEK_END:
|
||||||
if hrs.size < 0 {
|
if hrs.size < 0 {
|
||||||
return 0, errors.New("content length not known")
|
return 0, errors.New("content length not known")
|
||||||
}
|
}
|
||||||
newOffset = hrs.size + int64(offset)
|
newOffset = hrs.size + offset
|
||||||
case os.SEEK_SET:
|
case os.SEEK_SET:
|
||||||
newOffset = int64(offset)
|
newOffset = offset
|
||||||
}
|
}
|
||||||
|
|
||||||
if newOffset < 0 {
|
if newOffset < 0 {
|
||||||
|
@ -131,7 +144,6 @@ func (hrs *httpReadSeeker) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
hrs.rc = nil
|
hrs.rc = nil
|
||||||
hrs.brd = nil
|
|
||||||
|
|
||||||
hrs.err = errors.New("httpLayer: closed")
|
hrs.err = errors.New("httpLayer: closed")
|
||||||
|
|
||||||
|
@ -154,7 +166,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if hrs.rc != nil {
|
if hrs.rc != nil {
|
||||||
return hrs.brd, nil
|
return hrs.rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", hrs.url, nil)
|
req, err := http.NewRequest("GET", hrs.url, nil)
|
||||||
|
@ -163,10 +175,8 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if hrs.readerOffset > 0 {
|
if hrs.readerOffset > 0 {
|
||||||
// TODO(stevvooe): Get this working correctly.
|
|
||||||
|
|
||||||
// If we are at different offset, issue a range request from there.
|
// If we are at different offset, issue a range request from there.
|
||||||
req.Header.Add("Range", "1-")
|
req.Header.Add("Range", fmt.Sprintf("bytes=%d-", hrs.readerOffset))
|
||||||
// TODO: get context in here
|
// TODO: get context in here
|
||||||
// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
|
// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
|
||||||
}
|
}
|
||||||
|
@ -179,12 +189,55 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
// Normally would use client.SuccessStatus, but that would be a cyclic
|
// Normally would use client.SuccessStatus, but that would be a cyclic
|
||||||
// import
|
// import
|
||||||
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
|
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
|
||||||
hrs.rc = resp.Body
|
if hrs.readerOffset > 0 {
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode != http.StatusPartialContent {
|
||||||
|
return nil, ErrWrongCodeForByteRange
|
||||||
|
}
|
||||||
|
|
||||||
|
contentRange := resp.Header.Get("Content-Range")
|
||||||
|
if contentRange == "" {
|
||||||
|
return nil, errors.New("no Content-Range header found in HTTP 206 response")
|
||||||
|
}
|
||||||
|
|
||||||
|
submatches := contentRangeRegexp.FindStringSubmatch(contentRange)
|
||||||
|
if len(submatches) < 4 {
|
||||||
|
return nil, fmt.Errorf("could not parse Content-Range header: %s", contentRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
startByte, err := strconv.ParseUint(submatches[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse start of range in Content-Range header: %s", contentRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
if startByte != uint64(hrs.readerOffset) {
|
||||||
|
return nil, fmt.Errorf("received Content-Range starting at offset %d instead of requested %d", startByte, hrs.readerOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
endByte, err := strconv.ParseUint(submatches[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse end of range in Content-Range header: %s", contentRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
if submatches[3] == "*" {
|
||||||
|
hrs.size = -1
|
||||||
|
} else {
|
||||||
|
size, err := strconv.ParseUint(submatches[3], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse total size in Content-Range header: %s", contentRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endByte+1 != size {
|
||||||
|
return nil, fmt.Errorf("range in Content-Range stops before the end of the content: %s", contentRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
hrs.size = int64(size)
|
||||||
|
}
|
||||||
|
} else if resp.StatusCode == http.StatusOK {
|
||||||
hrs.size = resp.ContentLength
|
hrs.size = resp.ContentLength
|
||||||
} else {
|
} else {
|
||||||
hrs.size = -1
|
hrs.size = -1
|
||||||
}
|
}
|
||||||
|
hrs.rc = resp.Body
|
||||||
} else {
|
} else {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if hrs.errorHandler != nil {
|
if hrs.errorHandler != nil {
|
||||||
|
@ -193,11 +246,5 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
|
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hrs.brd == nil {
|
return hrs.rc, nil
|
||||||
hrs.brd = bufio.NewReader(hrs.rc)
|
|
||||||
} else {
|
|
||||||
hrs.brd.Reset(hrs.rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hrs.brd, nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue