|
@@ -8,6 +8,7 @@ import (
|
|
"net/url"
|
|
"net/url"
|
|
"os"
|
|
"os"
|
|
"runtime"
|
|
"runtime"
|
|
|
|
+ "strings"
|
|
|
|
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
|
@@ -61,7 +62,7 @@ type v2Puller struct {
|
|
confirmedV2 bool
|
|
confirmedV2 bool
|
|
}
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
|
|
|
|
+func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform string) (err error) {
|
|
// TODO(tiborvass): was ReceiveTimeout
|
|
// TODO(tiborvass): was ReceiveTimeout
|
|
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
|
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -69,7 +70,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if err = p.pullV2Repository(ctx, ref); err != nil {
|
|
|
|
|
|
+ if err = p.pullV2Repository(ctx, ref, platform); err != nil {
|
|
if _, ok := err.(fallbackError); ok {
|
|
if _, ok := err.(fallbackError); ok {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -84,10 +85,10 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
|
|
|
|
|
|
+func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, platform string) (err error) {
|
|
var layersDownloaded bool
|
|
var layersDownloaded bool
|
|
if !reference.IsNameOnly(ref) {
|
|
if !reference.IsNameOnly(ref) {
|
|
- layersDownloaded, err = p.pullV2Tag(ctx, ref)
|
|
|
|
|
|
+ layersDownloaded, err = p.pullV2Tag(ctx, ref, platform)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -109,7 +110,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- pulledNew, err := p.pullV2Tag(ctx, tagRef)
|
|
|
|
|
|
+ pulledNew, err := p.pullV2Tag(ctx, tagRef, platform)
|
|
if err != nil {
|
|
if err != nil {
|
|
// Since this is the pull-all-tags case, don't
|
|
// Since this is the pull-all-tags case, don't
|
|
// allow an error pulling a particular tag to
|
|
// allow an error pulling a particular tag to
|
|
@@ -325,7 +326,7 @@ func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
|
|
ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
|
|
ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
|
|
}
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) {
|
|
|
|
|
|
+func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string) (tagUpdated bool, err error) {
|
|
manSvc, err := p.repo.Manifests(ctx)
|
|
manSvc, err := p.repo.Manifests(ctx)
|
|
if err != nil {
|
|
if err != nil {
|
|
return false, err
|
|
return false, err
|
|
@@ -389,17 +390,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
|
|
if p.config.RequireSchema2 {
|
|
if p.config.RequireSchema2 {
|
|
return false, fmt.Errorf("invalid manifest: not schema2")
|
|
return false, fmt.Errorf("invalid manifest: not schema2")
|
|
}
|
|
}
|
|
- id, manifestDigest, err = p.pullSchema1(ctx, ref, v)
|
|
|
|
|
|
+ id, manifestDigest, err = p.pullSchema1(ctx, ref, v, os)
|
|
if err != nil {
|
|
if err != nil {
|
|
return false, err
|
|
return false, err
|
|
}
|
|
}
|
|
case *schema2.DeserializedManifest:
|
|
case *schema2.DeserializedManifest:
|
|
- id, manifestDigest, err = p.pullSchema2(ctx, ref, v)
|
|
|
|
|
|
+ id, manifestDigest, err = p.pullSchema2(ctx, ref, v, os)
|
|
if err != nil {
|
|
if err != nil {
|
|
return false, err
|
|
return false, err
|
|
}
|
|
}
|
|
case *manifestlist.DeserializedManifestList:
|
|
case *manifestlist.DeserializedManifestList:
|
|
- id, manifestDigest, err = p.pullManifestList(ctx, ref, v)
|
|
|
|
|
|
+ id, manifestDigest, err = p.pullManifestList(ctx, ref, v, os)
|
|
if err != nil {
|
|
if err != nil {
|
|
return false, err
|
|
return false, err
|
|
}
|
|
}
|
|
@@ -435,7 +436,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
|
|
return true, nil
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
|
|
|
+func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
var verifiedManifest *schema1.Manifest
|
|
var verifiedManifest *schema1.Manifest
|
|
verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
|
|
verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -490,7 +491,8 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|
// The v1 manifest itself doesn't directly contain a platform. However,
|
|
// The v1 manifest itself doesn't directly contain a platform. However,
|
|
// the history does, but unfortunately that's a string, so search through
|
|
// the history does, but unfortunately that's a string, so search through
|
|
// all the history until hopefully we find one which indicates the os.
|
|
// all the history until hopefully we find one which indicates the os.
|
|
- platform := runtime.GOOS
|
|
|
|
|
|
+ // supertest2014/nyan is an example of a registry image with schemav1.
|
|
|
|
+ configOS := runtime.GOOS
|
|
if system.LCOWSupported() {
|
|
if system.LCOWSupported() {
|
|
type config struct {
|
|
type config struct {
|
|
Os string `json:"os,omitempty"`
|
|
Os string `json:"os,omitempty"`
|
|
@@ -499,14 +501,20 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|
var c config
|
|
var c config
|
|
if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
|
|
if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
|
|
if c.Os != "" {
|
|
if c.Os != "" {
|
|
- platform = c.Os
|
|
|
|
|
|
+ configOS = c.Os
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.Platform(platform), descriptors, p.config.ProgressOutput)
|
|
|
|
|
|
+ // Early bath if the requested OS doesn't match that of the configuration.
|
|
|
|
+ // This avoids doing the download, only to potentially fail later.
|
|
|
|
+ if !strings.EqualFold(string(configOS), requestedOS) {
|
|
|
|
+ return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.OS(configOS), descriptors, p.config.ProgressOutput)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
}
|
|
}
|
|
@@ -527,7 +535,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|
return imageID, manifestDigest, nil
|
|
return imageID, manifestDigest, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
|
|
|
+func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
manifestDigest, err = schema2ManifestDigest(ref, mfst)
|
|
manifestDigest, err = schema2ManifestDigest(ref, mfst)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
@@ -576,11 +584,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
}()
|
|
}()
|
|
|
|
|
|
var (
|
|
var (
|
|
- configJSON []byte // raw serialized image config
|
|
|
|
- downloadedRootFS *image.RootFS // rootFS from registered layers
|
|
|
|
- configRootFS *image.RootFS // rootFS from configuration
|
|
|
|
- release func() // release resources from rootFS download
|
|
|
|
- platform layer.Platform // for LCOW when registering downloaded layers
|
|
|
|
|
|
+ configJSON []byte // raw serialized image config
|
|
|
|
+ downloadedRootFS *image.RootFS // rootFS from registered layers
|
|
|
|
+ configRootFS *image.RootFS // rootFS from configuration
|
|
|
|
+ release func() // release resources from rootFS download
|
|
|
|
+ configOS layer.OS // for LCOW when registering downloaded layers
|
|
)
|
|
)
|
|
|
|
|
|
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
|
|
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
|
|
@@ -592,7 +600,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
// check to block Windows images being pulled on Linux is implemented, it
|
|
// check to block Windows images being pulled on Linux is implemented, it
|
|
// may be necessary to perform the same type of serialisation.
|
|
// may be necessary to perform the same type of serialisation.
|
|
if runtime.GOOS == "windows" {
|
|
if runtime.GOOS == "windows" {
|
|
- configJSON, configRootFS, platform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
|
|
|
|
+ configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
}
|
|
}
|
|
@@ -605,6 +613,12 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
return "", "", errRootFSMismatch
|
|
return "", "", errRootFSMismatch
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Early bath if the requested OS doesn't match that of the configuration.
|
|
|
|
+ // This avoids doing the download, only to potentially fail later.
|
|
|
|
+ if !strings.EqualFold(string(configOS), requestedOS) {
|
|
|
|
+ return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
|
|
|
|
+ }
|
|
|
|
+
|
|
// Populate diff ids in descriptors to avoid downloading foreign layers
|
|
// Populate diff ids in descriptors to avoid downloading foreign layers
|
|
// which have been side loaded
|
|
// which have been side loaded
|
|
for i := range descriptors {
|
|
for i := range descriptors {
|
|
@@ -619,7 +633,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
rootFS image.RootFS
|
|
rootFS image.RootFS
|
|
)
|
|
)
|
|
downloadRootFS := *image.NewRootFS()
|
|
downloadRootFS := *image.NewRootFS()
|
|
- rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, platform, descriptors, p.config.ProgressOutput)
|
|
|
|
|
|
+ rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, layer.OS(requestedOS), descriptors, p.config.ProgressOutput)
|
|
if err != nil {
|
|
if err != nil {
|
|
// Intentionally do not cancel the config download here
|
|
// Intentionally do not cancel the config download here
|
|
// as the error from config download (if there is one)
|
|
// as the error from config download (if there is one)
|
|
@@ -637,7 +651,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
}
|
|
}
|
|
|
|
|
|
if configJSON == nil {
|
|
if configJSON == nil {
|
|
- configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
|
|
|
|
+ configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
if err == nil && configRootFS == nil {
|
|
if err == nil && configRootFS == nil {
|
|
err = errRootFSInvalid
|
|
err = errRootFSInvalid
|
|
}
|
|
}
|
|
@@ -684,14 +698,14 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
return imageID, manifestDigest, nil
|
|
return imageID, manifestDigest, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.Platform, error) {
|
|
|
|
|
|
+func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.OS, error) {
|
|
select {
|
|
select {
|
|
case configJSON := <-configChan:
|
|
case configJSON := <-configChan:
|
|
- rootfs, platform, err := s.RootFSAndPlatformFromConfig(configJSON)
|
|
|
|
|
|
+ rootfs, os, err := s.RootFSAndOSFromConfig(configJSON)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
return nil, nil, "", err
|
|
}
|
|
}
|
|
- return configJSON, rootfs, platform, nil
|
|
|
|
|
|
+ return configJSON, rootfs, os, nil
|
|
case err := <-errChan:
|
|
case err := <-errChan:
|
|
return nil, nil, "", err
|
|
return nil, nil, "", err
|
|
// Don't need a case for ctx.Done in the select because cancellation
|
|
// Don't need a case for ctx.Done in the select because cancellation
|
|
@@ -701,18 +715,18 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan
|
|
|
|
|
|
// pullManifestList handles "manifest lists" which point to various
|
|
// pullManifestList handles "manifest lists" which point to various
|
|
// platform-specific manifests.
|
|
// platform-specific manifests.
|
|
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
|
|
|
|
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, os string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
|
|
manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
}
|
|
}
|
|
|
|
|
|
- logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a os/arch match", ref, len(mfstList.Manifests))
|
|
|
|
|
|
+ logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
|
|
|
|
|
|
manifestMatches := filterManifests(mfstList.Manifests)
|
|
manifestMatches := filterManifests(mfstList.Manifests)
|
|
|
|
|
|
if len(manifestMatches) == 0 {
|
|
if len(manifestMatches) == 0 {
|
|
- errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", runtime.GOOS, runtime.GOARCH)
|
|
|
|
|
|
+ errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
|
|
logrus.Debugf(errMsg)
|
|
logrus.Debugf(errMsg)
|
|
return "", "", errors.New(errMsg)
|
|
return "", "", errors.New(errMsg)
|
|
}
|
|
}
|
|
@@ -739,12 +753,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
|
|
|
|
|
|
switch v := manifest.(type) {
|
|
switch v := manifest.(type) {
|
|
case *schema1.SignedManifest:
|
|
case *schema1.SignedManifest:
|
|
- id, _, err = p.pullSchema1(ctx, manifestRef, v)
|
|
|
|
|
|
+ id, _, err = p.pullSchema1(ctx, manifestRef, v, os)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
}
|
|
}
|
|
case *schema2.DeserializedManifest:
|
|
case *schema2.DeserializedManifest:
|
|
- id, _, err = p.pullSchema2(ctx, manifestRef, v)
|
|
|
|
|
|
+ id, _, err = p.pullSchema2(ctx, manifestRef, v, os)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", "", err
|
|
return "", "", err
|
|
}
|
|
}
|