2018-02-05 21:05:59 +00:00
package distribution // import "github.com/docker/docker/distribution"
2015-11-18 22:18:44 +00:00
import (
2018-04-19 22:30:59 +00:00
"context"
2015-11-18 22:18:44 +00:00
"encoding/json"
"fmt"
"io"
"io/ioutil"
2016-02-11 18:30:56 +00:00
"net/url"
2015-11-18 22:18:44 +00:00
"os"
"runtime"
2017-08-08 19:43:48 +00:00
"strings"
2015-11-18 22:18:44 +00:00
2018-06-26 21:49:33 +00:00
"github.com/containerd/containerd/platforms"
2015-11-18 22:18:44 +00:00
"github.com/docker/distribution"
2015-12-17 03:19:22 +00:00
"github.com/docker/distribution/manifest/manifestlist"
2015-11-18 22:18:44 +00:00
"github.com/docker/distribution/manifest/schema1"
2015-12-11 23:24:12 +00:00
"github.com/docker/distribution/manifest/schema2"
2017-01-26 00:54:18 +00:00
"github.com/docker/distribution/reference"
2015-12-21 23:42:04 +00:00
"github.com/docker/distribution/registry/api/errcode"
2016-02-11 18:30:56 +00:00
"github.com/docker/distribution/registry/client/auth"
2016-01-26 19:19:18 +00:00
"github.com/docker/distribution/registry/client/transport"
2015-11-18 22:18:44 +00:00
"github.com/docker/docker/distribution/metadata"
2015-11-14 00:59:01 +00:00
"github.com/docker/docker/distribution/xfer"
2015-11-18 22:18:44 +00:00
"github.com/docker/docker/image"
2019-06-15 01:56:28 +00:00
v1 "github.com/docker/docker/image/v1"
2015-11-18 22:18:44 +00:00
"github.com/docker/docker/layer"
2015-11-14 00:59:01 +00:00
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress"
2015-11-18 22:18:44 +00:00
"github.com/docker/docker/pkg/stringid"
2017-06-20 02:42:48 +00:00
"github.com/docker/docker/pkg/system"
2017-01-26 00:54:18 +00:00
refstore "github.com/docker/docker/reference"
2015-11-18 22:18:44 +00:00
"github.com/docker/docker/registry"
2018-05-19 11:38:54 +00:00
"github.com/opencontainers/go-digest"
2018-02-15 21:17:27 +00:00
specs "github.com/opencontainers/image-spec/specs-go/v1"
2017-07-19 14:20:13 +00:00
"github.com/pkg/errors"
2017-07-26 21:42:13 +00:00
"github.com/sirupsen/logrus"
2015-11-18 22:18:44 +00:00
)
2016-08-10 19:04:42 +00:00
var (
2016-12-16 19:19:05 +00:00
errRootFSMismatch = errors . New ( "layers from manifest don't match image configuration" )
errRootFSInvalid = errors . New ( "invalid rootfs in image configuration" )
2016-08-10 19:04:42 +00:00
)
2015-12-11 23:24:12 +00:00
2016-02-11 22:08:49 +00:00
// ImageConfigPullError is an error pulling the image config blob
// (only applies to schema2).
type ImageConfigPullError struct {
Err error
}
// Error returns the error string for ImageConfigPullError.
func ( e ImageConfigPullError ) Error ( ) string {
return "error pulling image configuration: " + e . Err . Error ( )
}
2015-11-18 22:18:44 +00:00
type v2Puller struct {
2016-09-21 15:01:09 +00:00
V2MetadataService metadata . V2MetadataService
2016-01-14 03:34:27 +00:00
endpoint registry . APIEndpoint
config * ImagePullConfig
repoInfo * registry . RepositoryInfo
repo distribution . Repository
2015-12-04 21:42:33 +00:00
// confirmedV2 is set to true if we confirm we're talking to a v2
// registry. This is used to limit fallbacks to the v1 protocol.
confirmedV2 bool
2015-11-18 22:18:44 +00:00
}
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) Pull ( ctx context . Context , ref reference . Named , platform * specs . Platform ) ( err error ) {
2015-11-18 22:18:44 +00:00
// TODO(tiborvass): was ReceiveTimeout
2015-12-04 21:42:33 +00:00
p . repo , p . confirmedV2 , err = NewV2Repository ( ctx , p . repoInfo , p . endpoint , p . config . MetaHeaders , p . config . AuthConfig , "pull" )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-11-14 00:59:01 +00:00
logrus . Warnf ( "Error getting v2 registry: %v" , err )
2016-02-11 23:45:29 +00:00
return err
2015-11-18 22:18:44 +00:00
}
2018-06-26 21:49:33 +00:00
if err = p . pullV2Repository ( ctx , ref , platform ) ; err != nil {
2015-12-23 23:21:43 +00:00
if _ , ok := err . ( fallbackError ) ; ok {
return err
}
2017-11-15 00:06:17 +00:00
if continueOnError ( err , p . endpoint . Mirror ) {
2016-02-11 23:45:29 +00:00
return fallbackError {
err : err ,
confirmedV2 : p . confirmedV2 ,
transportOK : true ,
}
2015-11-18 22:18:44 +00:00
}
}
2015-12-04 21:42:33 +00:00
return err
2015-11-18 22:18:44 +00:00
}
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) pullV2Repository ( ctx context . Context , ref reference . Named , platform * specs . Platform ) ( err error ) {
2015-12-23 23:21:43 +00:00
var layersDownloaded bool
2015-12-10 19:01:34 +00:00
if ! reference . IsNameOnly ( ref ) {
2018-06-26 21:49:33 +00:00
layersDownloaded , err = p . pullV2Tag ( ctx , ref , platform )
2015-12-23 23:21:43 +00:00
if err != nil {
return err
}
2015-11-18 22:18:44 +00:00
} else {
2015-12-08 19:14:02 +00:00
tags , err := p . repo . Tags ( ctx ) . All ( ctx )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-12-23 23:21:43 +00:00
// If this repository doesn't exist on V2, we should
// permit a fallback to V1.
return allowV1Fallback ( err )
2015-11-18 22:18:44 +00:00
}
2015-12-23 23:21:43 +00:00
// The v2 registry knows about this repository, so we will not
// allow fallback to the v1 protocol even if we encounter an
// error later on.
2015-12-04 21:42:33 +00:00
p . confirmedV2 = true
2015-11-18 22:18:44 +00:00
for _ , tag := range tags {
2015-12-11 19:00:13 +00:00
tagRef , err := reference . WithTag ( ref , tag )
2015-11-18 22:18:44 +00:00
if err != nil {
return err
}
2018-06-26 21:49:33 +00:00
pulledNew , err := p . pullV2Tag ( ctx , tagRef , platform )
2015-12-23 23:21:43 +00:00
if err != nil {
// Since this is the pull-all-tags case, don't
// allow an error pulling a particular tag to
// make the whole pull fall back to v1.
if fallbackErr , ok := err . ( fallbackError ) ; ok {
return fallbackErr . err
}
return err
}
// pulledNew is true if either new layers were downloaded OR if existing images were newly tagged
// TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus?
layersDownloaded = layersDownloaded || pulledNew
2015-11-18 22:18:44 +00:00
}
}
2017-01-26 00:54:18 +00:00
writeStatus ( reference . FamiliarString ( ref ) , p . config . ProgressOutput , layersDownloaded )
2015-11-18 22:18:44 +00:00
return nil
}
2015-11-14 00:59:01 +00:00
type v2LayerDescriptor struct {
2016-01-14 03:34:27 +00:00
digest digest . Digest
2017-05-05 17:56:40 +00:00
diffID layer . DiffID
2016-01-14 03:34:27 +00:00
repoInfo * registry . RepositoryInfo
repo distribution . Repository
2016-09-21 15:01:09 +00:00
V2MetadataService metadata . V2MetadataService
2016-01-26 02:20:18 +00:00
tmpFile * os . File
2016-01-26 19:19:18 +00:00
verifier digest . Verifier
2016-06-07 00:49:34 +00:00
src distribution . Descriptor
2015-11-18 22:18:44 +00:00
}
2015-11-14 00:59:01 +00:00
func ( ld * v2LayerDescriptor ) Key ( ) string {
return "v2:" + ld . digest . String ( )
}
2015-11-18 22:18:44 +00:00
2015-11-14 00:59:01 +00:00
func ( ld * v2LayerDescriptor ) ID ( ) string {
return stringid . TruncateID ( ld . digest . String ( ) )
}
2015-11-18 22:18:44 +00:00
2015-11-14 00:59:01 +00:00
func ( ld * v2LayerDescriptor ) DiffID ( ) ( layer . DiffID , error ) {
2017-05-05 17:56:40 +00:00
if ld . diffID != "" {
return ld . diffID , nil
}
2016-01-14 03:34:27 +00:00
return ld . V2MetadataService . GetDiffID ( ld . digest )
2015-11-14 00:59:01 +00:00
}
func ( ld * v2LayerDescriptor ) Download ( ctx context . Context , progressOutput progress . Output ) ( io . ReadCloser , int64 , error ) {
logrus . Debugf ( "pulling blob %q" , ld . digest )
2015-11-18 22:18:44 +00:00
2016-01-26 19:19:18 +00:00
var (
err error
offset int64
)
2016-01-26 02:20:18 +00:00
if ld . tmpFile == nil {
ld . tmpFile , err = createDownloadFile ( )
2016-01-26 19:19:18 +00:00
if err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
2016-01-26 02:20:18 +00:00
} else {
2016-01-26 19:19:18 +00:00
offset , err = ld . tmpFile . Seek ( 0 , os . SEEK_END )
if err != nil {
logrus . Debugf ( "error seeking to end of download file: %v" , err )
offset = 0
ld . tmpFile . Close ( )
if err := os . Remove ( ld . tmpFile . Name ( ) ) ; err != nil {
logrus . Errorf ( "Failed to remove temp file: %s" , ld . tmpFile . Name ( ) )
}
ld . tmpFile , err = createDownloadFile ( )
if err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
} else if offset != 0 {
logrus . Debugf ( "attempting to resume download of %q from %d bytes" , ld . digest , offset )
}
2016-01-26 02:20:18 +00:00
}
tmpFile := ld . tmpFile
2015-11-18 22:18:44 +00:00
2016-05-26 02:11:51 +00:00
layerDownload , err := ld . open ( ctx )
2015-11-18 22:18:44 +00:00
if err != nil {
2016-02-11 22:08:49 +00:00
logrus . Errorf ( "Error initiating layer download: %v" , err )
2015-11-14 00:59:01 +00:00
return nil , 0 , retryOnError ( err )
2015-11-18 22:18:44 +00:00
}
2016-01-26 19:19:18 +00:00
if offset != 0 {
_ , err := layerDownload . Seek ( offset , os . SEEK_SET )
if err != nil {
if err := ld . truncateDownloadFile ( ) ; err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
return nil , 0 , err
}
}
2015-11-14 00:59:01 +00:00
size , err := layerDownload . Seek ( 0 , os . SEEK_END )
2015-12-04 02:23:21 +00:00
if err != nil {
// Seek failed, perhaps because there was no Content-Length
// header. This shouldn't fail the download, because we can
// still continue without a progress bar.
2015-11-14 00:59:01 +00:00
size = 0
2015-12-04 02:23:21 +00:00
} else {
2016-01-26 19:19:18 +00:00
if size != 0 && offset > size {
2016-06-11 20:16:55 +00:00
logrus . Debug ( "Partial download is larger than full blob. Starting over" )
2016-01-26 19:19:18 +00:00
offset = 0
if err := ld . truncateDownloadFile ( ) ; err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
}
// Restore the seek offset either at the beginning of the
// stream, or just after the last byte we have from previous
// attempts.
_ , err = layerDownload . Seek ( offset , os . SEEK_SET )
2015-12-04 02:23:21 +00:00
if err != nil {
2015-11-14 00:59:01 +00:00
return nil , 0 , err
2015-12-04 02:23:21 +00:00
}
}
2016-01-26 19:19:18 +00:00
reader := progress . NewProgressReader ( ioutils . NewCancelReadCloser ( ctx , layerDownload ) , progressOutput , size - offset , ld . ID ( ) , "Downloading" )
2015-11-14 00:59:01 +00:00
defer reader . Close ( )
2016-01-26 19:19:18 +00:00
if ld . verifier == nil {
2017-01-07 01:23:18 +00:00
ld . verifier = ld . digest . Verifier ( )
2015-11-18 22:18:44 +00:00
}
2016-01-26 19:19:18 +00:00
_ , err = io . Copy ( tmpFile , io . TeeReader ( reader , ld . verifier ) )
2015-11-14 00:59:01 +00:00
if err != nil {
2016-01-26 19:19:18 +00:00
if err == transport . ErrWrongCodeForByteRange {
if err := ld . truncateDownloadFile ( ) ; err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
return nil , 0 , err
2016-01-26 02:20:18 +00:00
}
2015-11-14 00:59:01 +00:00
return nil , 0 , retryOnError ( err )
}
2015-11-18 22:18:44 +00:00
2015-11-14 00:59:01 +00:00
progress . Update ( progressOutput , ld . ID ( ) , "Verifying Checksum" )
2015-11-18 22:18:44 +00:00
2016-01-26 19:19:18 +00:00
if ! ld . verifier . Verified ( ) {
2015-11-14 00:59:01 +00:00
err = fmt . Errorf ( "filesystem layer verification failed for digest %s" , ld . digest )
2015-11-18 22:18:44 +00:00
logrus . Error ( err )
2016-01-26 02:20:18 +00:00
2016-01-26 19:19:18 +00:00
// Allow a retry if this digest verification error happened
// after a resumed download.
if offset != 0 {
if err := ld . truncateDownloadFile ( ) ; err != nil {
return nil , 0 , xfer . DoNotRetry { Err : err }
}
2015-11-14 00:59:01 +00:00
2016-01-26 19:19:18 +00:00
return nil , 0 , err
}
2015-11-14 00:59:01 +00:00
return nil , 0 , xfer . DoNotRetry { Err : err }
2015-11-18 22:18:44 +00:00
}
2015-11-14 00:59:01 +00:00
progress . Update ( progressOutput , ld . ID ( ) , "Download complete" )
2015-11-18 22:18:44 +00:00
2015-11-14 00:59:01 +00:00
logrus . Debugf ( "Downloaded %s to tempfile %s" , ld . ID ( ) , tmpFile . Name ( ) )
2016-01-26 02:20:18 +00:00
_ , err = tmpFile . Seek ( 0 , os . SEEK_SET )
if err != nil {
tmpFile . Close ( )
if err := os . Remove ( tmpFile . Name ( ) ) ; err != nil {
logrus . Errorf ( "Failed to remove temp file: %s" , tmpFile . Name ( ) )
}
2016-01-26 02:20:18 +00:00
ld . tmpFile = nil
2016-01-26 19:19:18 +00:00
ld . verifier = nil
2016-01-26 02:20:18 +00:00
return nil , 0 , xfer . DoNotRetry { Err : err }
}
2016-03-31 02:26:12 +00:00
// hand off the temporary file to the download manager, so it will only
// be closed once
ld . tmpFile = nil
return ioutils . NewReadCloserWrapper ( tmpFile , func ( ) error {
tmpFile . Close ( )
err := os . RemoveAll ( tmpFile . Name ( ) )
if err != nil {
logrus . Errorf ( "Failed to remove temp file: %s" , tmpFile . Name ( ) )
}
return err
} ) , size , nil
2016-01-26 02:20:18 +00:00
}
func ( ld * v2LayerDescriptor ) Close ( ) {
if ld . tmpFile != nil {
ld . tmpFile . Close ( )
if err := os . RemoveAll ( ld . tmpFile . Name ( ) ) ; err != nil {
logrus . Errorf ( "Failed to remove temp file: %s" , ld . tmpFile . Name ( ) )
}
}
2015-11-14 00:59:01 +00:00
}
2015-11-18 22:18:44 +00:00
2016-01-26 19:19:18 +00:00
func ( ld * v2LayerDescriptor ) truncateDownloadFile ( ) error {
// Need a new hash context since we will be redoing the download
ld . verifier = nil
if _ , err := ld . tmpFile . Seek ( 0 , os . SEEK_SET ) ; err != nil {
2016-02-11 22:08:49 +00:00
logrus . Errorf ( "error seeking to beginning of download file: %v" , err )
2016-01-26 19:19:18 +00:00
return err
}
if err := ld . tmpFile . Truncate ( 0 ) ; err != nil {
2016-02-11 22:08:49 +00:00
logrus . Errorf ( "error truncating download file: %v" , err )
2016-01-26 19:19:18 +00:00
return err
}
return nil
}
2015-11-14 00:59:01 +00:00
func ( ld * v2LayerDescriptor ) Registered ( diffID layer . DiffID ) {
// Cache mapping from this layer's DiffID to the blobsum
2017-01-26 00:54:18 +00:00
ld . V2MetadataService . Add ( diffID , metadata . V2Metadata { Digest : ld . digest , SourceRepository : ld . repoInfo . Name . Name ( ) } )
2015-11-18 22:18:44 +00:00
}
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) pullV2Tag ( ctx context . Context , ref reference . Named , platform * specs . Platform ) ( tagUpdated bool , err error ) {
2015-12-08 19:14:02 +00:00
manSvc , err := p . repo . Manifests ( ctx )
if err != nil {
return false , err
}
var (
manifest distribution . Manifest
tagOrDigest string // Used for logging/progress only
)
2017-05-16 00:17:27 +00:00
if digested , isDigested := ref . ( reference . Canonical ) ; isDigested {
2015-12-08 19:14:02 +00:00
manifest , err = manSvc . Get ( ctx , digested . Digest ( ) )
if err != nil {
return false , err
}
2015-11-18 22:18:44 +00:00
tagOrDigest = digested . Digest ( ) . String ( )
2017-05-16 00:17:27 +00:00
} else if tagged , isTagged := ref . ( reference . NamedTagged ) ; isTagged {
manifest , err = manSvc . Get ( ctx , "" , distribution . WithTag ( tagged . Tag ( ) ) )
if err != nil {
return false , allowV1Fallback ( err )
}
tagOrDigest = tagged . Tag ( )
2015-11-18 22:18:44 +00:00
} else {
2017-01-26 00:54:18 +00:00
return false , fmt . Errorf ( "internal error: reference has neither a tag nor a digest: %s" , reference . FamiliarString ( ref ) )
2015-11-18 22:18:44 +00:00
}
2015-12-08 19:14:02 +00:00
if manifest == nil {
return false , fmt . Errorf ( "image manifest does not exist for tag or digest %q" , tagOrDigest )
}
2015-11-18 22:18:44 +00:00
2016-08-10 19:04:42 +00:00
if m , ok := manifest . ( * schema2 . DeserializedManifest ) ; ok {
2016-12-16 19:19:05 +00:00
var allowedMediatype bool
for _ , t := range p . config . Schema2Types {
if m . Manifest . Config . MediaType == t {
allowedMediatype = true
break
}
}
if ! allowedMediatype {
configClass := mediaTypeClasses [ m . Manifest . Config . MediaType ]
if configClass == "" {
configClass = "unknown"
}
2017-07-19 14:20:13 +00:00
return false , invalidManifestClassError { m . Manifest . Config . MediaType , configClass }
2016-08-10 19:04:42 +00:00
}
}
2015-12-08 19:14:02 +00:00
// If manSvc.Get succeeded, we can be confident that the registry on
// the other side speaks the v2 protocol.
p . confirmedV2 = true
2017-01-26 00:54:18 +00:00
logrus . Debugf ( "Pulling ref from V2 registry: %s" , reference . FamiliarString ( ref ) )
progress . Message ( p . config . ProgressOutput , tagOrDigest , "Pulling from " + reference . FamiliarName ( p . repo . Named ( ) ) )
2015-12-08 19:14:02 +00:00
2015-12-11 23:24:12 +00:00
var (
2016-09-15 23:37:32 +00:00
id digest . Digest
2015-12-11 23:24:12 +00:00
manifestDigest digest . Digest
)
2015-12-08 19:14:02 +00:00
switch v := manifest . ( type ) {
case * schema1 . SignedManifest :
2016-12-16 19:19:05 +00:00
if p . config . RequireSchema2 {
return false , fmt . Errorf ( "invalid manifest: not schema2" )
}
2019-08-13 18:13:14 +00:00
// give registries time to upgrade to schema2 and only warn if we know a registry has been upgraded long time ago
// TODO: condition to be removed
if reference . Domain ( ref ) == "docker.io" {
msg := fmt . Sprintf ( "Image %s uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/" , ref )
logrus . Warn ( msg )
progress . Message ( p . config . ProgressOutput , "" , msg )
}
2019-06-15 01:56:28 +00:00
2018-06-26 21:49:33 +00:00
id , manifestDigest , err = p . pullSchema1 ( ctx , ref , v , platform )
2015-12-11 23:24:12 +00:00
if err != nil {
return false , err
}
case * schema2 . DeserializedManifest :
2018-06-26 21:49:33 +00:00
id , manifestDigest , err = p . pullSchema2 ( ctx , ref , v , platform )
2015-12-08 19:14:02 +00:00
if err != nil {
return false , err
}
2015-12-17 03:19:22 +00:00
case * manifestlist . DeserializedManifestList :
2018-06-26 21:49:33 +00:00
id , manifestDigest , err = p . pullManifestList ( ctx , ref , v , platform )
2015-12-17 03:19:22 +00:00
if err != nil {
return false , err
}
2015-12-08 19:14:02 +00:00
default :
2017-07-19 14:20:13 +00:00
return false , invalidManifestFormatError { }
2015-11-18 22:18:44 +00:00
}
2015-12-11 23:24:12 +00:00
progress . Message ( p . config . ProgressOutput , "" , "Digest: " + manifestDigest . String ( ) )
2016-12-16 19:19:05 +00:00
if p . config . ReferenceStore != nil {
oldTagID , err := p . config . ReferenceStore . Get ( ref )
if err == nil {
if oldTagID == id {
return false , addDigestReference ( p . config . ReferenceStore , ref , manifestDigest , id )
}
2017-01-26 00:54:18 +00:00
} else if err != refstore . ErrDoesNotExist {
2016-06-27 17:09:57 +00:00
return false , err
}
2016-12-16 19:19:05 +00:00
if canonical , ok := ref . ( reference . Canonical ) ; ok {
if err = p . config . ReferenceStore . AddDigest ( canonical , id , true ) ; err != nil {
return false , err
}
} else {
if err = addDigestReference ( p . config . ReferenceStore , ref , manifestDigest , id ) ; err != nil {
return false , err
}
if err = p . config . ReferenceStore . AddTag ( ref , id , true ) ; err != nil {
return false , err
}
2016-06-27 17:09:57 +00:00
}
2015-11-18 22:18:44 +00:00
}
2015-12-08 19:14:02 +00:00
return true , nil
}
2015-12-04 21:42:33 +00:00
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) pullSchema1 ( ctx context . Context , ref reference . Reference , unverifiedManifest * schema1 . SignedManifest , platform * specs . Platform ) ( id digest . Digest , manifestDigest digest . Digest , err error ) {
2015-11-18 22:18:44 +00:00
var verifiedManifest * schema1 . Manifest
2015-12-11 23:24:12 +00:00
verifiedManifest , err = verifySchema1Manifest ( unverifiedManifest , ref )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
rootFS := image . NewRootFS ( )
// remove duplicate layers and check parent chain validity
err = fixManifestLayers ( verifiedManifest )
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
2015-11-14 00:59:01 +00:00
var descriptors [ ] xfer . DownloadDescriptor
2015-11-18 22:18:44 +00:00
// Image history converted to the new format
var history [ ] image . History
// Note that the order of this loop is in the direction of bottom-most
// to top-most, so that the downloads slice gets ordered correctly.
for i := len ( verifiedManifest . FSLayers ) - 1 ; i >= 0 ; i -- {
blobSum := verifiedManifest . FSLayers [ i ] . BlobSum
var throwAway struct {
ThrowAway bool ` json:"throwaway,omitempty" `
}
if err := json . Unmarshal ( [ ] byte ( verifiedManifest . History [ i ] . V1Compatibility ) , & throwAway ) ; err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
h , err := v1 . HistoryFromConfig ( [ ] byte ( verifiedManifest . History [ i ] . V1Compatibility ) , throwAway . ThrowAway )
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
history = append ( history , h )
if throwAway . ThrowAway {
continue
}
2015-11-14 00:59:01 +00:00
layerDescriptor := & v2LayerDescriptor {
2016-01-14 03:34:27 +00:00
digest : blobSum ,
repoInfo : p . repoInfo ,
repo : p . repo ,
V2MetadataService : p . V2MetadataService ,
2015-11-18 22:18:44 +00:00
}
2015-11-14 00:59:01 +00:00
descriptors = append ( descriptors , layerDescriptor )
2015-11-18 22:18:44 +00:00
}
2017-08-24 18:48:16 +00:00
// The v1 manifest itself doesn't directly contain an OS. However,
2017-06-20 02:42:48 +00:00
// the history does, but unfortunately that's a string, so search through
2017-08-24 18:48:16 +00:00
// all the history until hopefully we find one which indicates the OS.
2017-08-08 19:43:48 +00:00
// supertest2014/nyan is an example of a registry image with schemav1.
configOS := runtime . GOOS
2017-06-26 16:11:54 +00:00
if system . LCOWSupported ( ) {
2017-06-20 02:42:48 +00:00
type config struct {
Os string ` json:"os,omitempty" `
}
for _ , v := range verifiedManifest . History {
var c config
if err := json . Unmarshal ( [ ] byte ( v . V1Compatibility ) , & c ) ; err == nil {
if c . Os != "" {
2017-08-08 19:43:48 +00:00
configOS = c . Os
2017-06-20 02:42:48 +00:00
break
}
}
}
}
2018-02-23 23:29:26 +00:00
// In the situation that the API call didn't specify an OS explicitly, but
// we support the operating system, switch to that operating system.
// eg FROM supertest2014/nyan with no platform specifier, and docker build
// with no --platform= flag under LCOW.
2018-06-26 21:49:33 +00:00
requestedOS := ""
if platform != nil {
requestedOS = platform . OS
} else if system . IsOSSupported ( configOS ) {
2018-02-23 23:29:26 +00:00
requestedOS = configOS
}
2017-08-08 19:43:48 +00:00
// Early bath if the requested OS doesn't match that of the configuration.
// This avoids doing the download, only to potentially fail later.
2017-09-13 19:49:04 +00:00
if ! strings . EqualFold ( configOS , requestedOS ) {
2017-08-08 19:43:48 +00:00
return "" , "" , fmt . Errorf ( "cannot download image with operating system %q when requesting %q" , configOS , requestedOS )
}
2017-08-24 18:48:16 +00:00
resultRootFS , release , err := p . config . DownloadManager . Download ( ctx , * rootFS , configOS , descriptors , p . config . ProgressOutput )
2015-11-14 00:59:01 +00:00
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
2015-11-14 00:59:01 +00:00
defer release ( )
2015-11-18 22:18:44 +00:00
2015-11-14 00:59:01 +00:00
config , err := v1 . MakeConfigFromV1Config ( [ ] byte ( verifiedManifest . History [ 0 ] . V1Compatibility ) , & resultRootFS , history )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
2016-12-16 19:19:05 +00:00
imageID , err := p . config . ImageStore . Put ( config )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
2015-11-18 22:18:44 +00:00
}
2015-12-11 23:24:12 +00:00
manifestDigest = digest . FromBytes ( unverifiedManifest . Canonical )
2016-12-16 19:19:05 +00:00
return imageID , manifestDigest , nil
2015-12-11 23:24:12 +00:00
}
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) pullSchema2 ( ctx context . Context , ref reference . Named , mfst * schema2 . DeserializedManifest , platform * specs . Platform ) ( id digest . Digest , manifestDigest digest . Digest , err error ) {
2015-12-17 03:19:22 +00:00
manifestDigest , err = schema2ManifestDigest ( ref , mfst )
2015-11-18 22:18:44 +00:00
if err != nil {
2015-12-11 23:24:12 +00:00
return "" , "" , err
}
target := mfst . Target ( )
2016-12-16 19:19:05 +00:00
if _ , err := p . config . ImageStore . Get ( target . Digest ) ; err == nil {
2015-12-11 23:24:12 +00:00
// If the image already exists locally, no need to pull
// anything.
2016-09-15 23:37:32 +00:00
return target . Digest , manifestDigest , nil
2015-12-11 23:24:12 +00:00
}
2016-05-26 02:11:51 +00:00
var descriptors [ ] xfer . DownloadDescriptor
// Note that the order of this loop is in the direction of bottom-most
// to top-most, so that the downloads slice gets ordered correctly.
for _ , d := range mfst . Layers {
layerDescriptor := & v2LayerDescriptor {
digest : d . Digest ,
repo : p . repo ,
repoInfo : p . repoInfo ,
V2MetadataService : p . V2MetadataService ,
2016-06-07 00:49:34 +00:00
src : d ,
2016-05-26 02:11:51 +00:00
}
descriptors = append ( descriptors , layerDescriptor )
}
2015-12-11 23:24:12 +00:00
configChan := make ( chan [ ] byte , 1 )
2017-01-31 01:04:14 +00:00
configErrChan := make ( chan error , 1 )
layerErrChan := make ( chan error , 1 )
downloadsDone := make ( chan struct { } )
2015-12-11 23:24:12 +00:00
var cancel func ( )
ctx , cancel = context . WithCancel ( ctx )
2017-01-31 01:04:14 +00:00
defer cancel ( )
2015-12-11 23:24:12 +00:00
// Pull the image config
go func ( ) {
2016-09-15 23:37:32 +00:00
configJSON , err := p . pullSchema2Config ( ctx , target . Digest )
2015-12-11 23:24:12 +00:00
if err != nil {
2017-01-31 01:04:14 +00:00
configErrChan <- ImageConfigPullError { Err : err }
2015-12-11 23:24:12 +00:00
cancel ( )
return
}
configChan <- configJSON
} ( )
2016-01-08 23:38:55 +00:00
var (
2018-02-15 21:17:27 +00:00
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
configPlatform * specs . Platform // for LCOW when registering downloaded layers
2016-01-08 23:38:55 +00:00
)
2016-07-22 22:29:21 +00:00
2018-06-26 21:49:33 +00:00
layerStoreOS := runtime . GOOS
if platform != nil {
layerStoreOS = platform . OS
}
2016-07-22 22:29:21 +00:00
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
2016-09-08 23:28:23 +00:00
// explicitly blocking images intended for linux from the Windows daemon. On
// Windows, we do this before the attempt to download, effectively serialising
// the download slightly slowing it down. We have to do it this way, as
// chances are the download of layers itself would fail due to file names
// which aren't suitable for NTFS. At some point in the future, if a similar
// check to block Windows images being pulled on Linux is implemented, it
// may be necessary to perform the same type of serialisation.
if runtime . GOOS == "windows" {
2018-02-15 21:17:27 +00:00
configJSON , configRootFS , configPlatform , err = receiveConfig ( p . config . ImageStore , configChan , configErrChan )
2016-09-08 23:28:23 +00:00
if err != nil {
return "" , "" , err
}
2016-12-16 19:19:05 +00:00
if configRootFS == nil {
2016-09-08 23:28:23 +00:00
return "" , "" , errRootFSInvalid
}
2018-02-15 21:17:27 +00:00
if err := checkImageCompatibility ( configPlatform . OS , configPlatform . OSVersion ) ; err != nil {
return "" , "" , err
}
2017-05-05 17:56:40 +00:00
if len ( descriptors ) != len ( configRootFS . DiffIDs ) {
return "" , "" , errRootFSMismatch
}
2018-06-26 21:49:33 +00:00
if platform == nil {
// Early bath if the requested OS doesn't match that of the configuration.
// This avoids doing the download, only to potentially fail later.
if ! system . IsOSSupported ( configPlatform . OS ) {
return "" , "" , fmt . Errorf ( "cannot download image with operating system %q when requesting %q" , configPlatform . OS , layerStoreOS )
}
layerStoreOS = configPlatform . OS
2017-08-08 19:43:48 +00:00
}
2017-05-05 17:56:40 +00:00
// Populate diff ids in descriptors to avoid downloading foreign layers
// which have been side loaded
for i := range descriptors {
descriptors [ i ] . ( * v2LayerDescriptor ) . diffID = configRootFS . DiffIDs [ i ]
}
2016-01-08 23:38:55 +00:00
}
2016-12-16 19:19:05 +00:00
if p . config . DownloadManager != nil {
2017-01-31 01:04:14 +00:00
go func ( ) {
var (
err error
rootFS image . RootFS
)
downloadRootFS := * image . NewRootFS ( )
2018-06-26 21:49:33 +00:00
rootFS , release , err = p . config . DownloadManager . Download ( ctx , downloadRootFS , layerStoreOS , descriptors , p . config . ProgressOutput )
2017-01-31 01:04:14 +00:00
if err != nil {
// Intentionally do not cancel the config download here
// as the error from config download (if there is one)
// is more interesting than the layer download error
layerErrChan <- err
return
2015-12-11 23:24:12 +00:00
}
2016-12-16 19:19:05 +00:00
2017-01-31 01:04:14 +00:00
downloadedRootFS = & rootFS
close ( downloadsDone )
} ( )
} else {
// We have nothing to download
close ( downloadsDone )
2015-12-11 23:24:12 +00:00
}
2016-09-08 23:28:23 +00:00
if configJSON == nil {
2017-09-13 19:49:04 +00:00
configJSON , configRootFS , _ , err = receiveConfig ( p . config . ImageStore , configChan , configErrChan )
2017-01-31 01:04:14 +00:00
if err == nil && configRootFS == nil {
err = errRootFSInvalid
}
2016-09-08 23:28:23 +00:00
if err != nil {
2017-01-31 01:04:14 +00:00
cancel ( )
select {
case <- downloadsDone :
case <- layerErrChan :
}
2016-09-08 23:28:23 +00:00
return "" , "" , err
}
2017-01-31 01:04:14 +00:00
}
2015-12-11 23:24:12 +00:00
2017-01-31 01:04:14 +00:00
select {
case <- downloadsDone :
case err = <- layerErrChan :
return "" , "" , err
}
if release != nil {
defer release ( )
2016-08-10 19:04:42 +00:00
}
2016-12-16 19:19:05 +00:00
if downloadedRootFS != nil {
// The DiffIDs returned in rootFS MUST match those in the config.
// Otherwise the image config could be referencing layers that aren't
// included in the manifest.
if len ( downloadedRootFS . DiffIDs ) != len ( configRootFS . DiffIDs ) {
2015-12-11 23:24:12 +00:00
return "" , "" , errRootFSMismatch
}
2016-12-16 19:19:05 +00:00
for i := range downloadedRootFS . DiffIDs {
if downloadedRootFS . DiffIDs [ i ] != configRootFS . DiffIDs [ i ] {
return "" , "" , errRootFSMismatch
}
}
2015-12-11 23:24:12 +00:00
}
2016-12-16 19:19:05 +00:00
imageID , err := p . config . ImageStore . Put ( configJSON )
2015-12-11 23:24:12 +00:00
if err != nil {
return "" , "" , err
}
2016-12-16 19:19:05 +00:00
return imageID , manifestDigest , nil
2015-12-11 23:24:12 +00:00
}
2018-02-15 21:17:27 +00:00
func receiveConfig ( s ImageConfigStore , configChan <- chan [ ] byte , errChan <- chan error ) ( [ ] byte , * image . RootFS , * specs . Platform , error ) {
2016-01-08 23:38:55 +00:00
select {
case configJSON := <- configChan :
2018-02-15 21:17:27 +00:00
rootfs , err := s . RootFSFromConfig ( configJSON )
if err != nil {
return nil , nil , nil , err
}
platform , err := s . PlatformFromConfig ( configJSON )
2016-12-16 19:19:05 +00:00
if err != nil {
2018-02-15 21:17:27 +00:00
return nil , nil , nil , err
2016-01-08 23:38:55 +00:00
}
2018-02-15 21:17:27 +00:00
return configJSON , rootfs , platform , nil
2016-01-08 23:38:55 +00:00
case err := <- errChan :
2018-02-15 21:17:27 +00:00
return nil , nil , nil , err
2016-01-08 23:38:55 +00:00
// Don't need a case for ctx.Done in the select because cancellation
// will trigger an error in p.pullSchema2ImageConfig.
}
}
2015-12-17 03:19:22 +00:00
// pullManifestList handles "manifest lists" which point to various
2017-01-17 04:45:27 +00:00
// platform-specific manifests.
2018-06-26 21:49:33 +00:00
func ( p * v2Puller ) pullManifestList ( ctx context . Context , ref reference . Named , mfstList * manifestlist . DeserializedManifestList , pp * specs . Platform ) ( id digest . Digest , manifestListDigest digest . Digest , err error ) {
2015-12-17 03:19:22 +00:00
manifestListDigest , err = schema2ManifestDigest ( ref , mfstList )
if err != nil {
return "" , "" , err
}
2018-06-26 21:49:33 +00:00
var platform specs . Platform
if pp != nil {
platform = * pp
2018-02-23 23:29:26 +00:00
}
2018-06-26 21:49:33 +00:00
logrus . Debugf ( "%s resolved to a manifestList object with %d entries; looking for a %s/%s match" , ref , len ( mfstList . Manifests ) , platforms . Format ( platform ) , runtime . GOARCH )
2015-12-17 03:19:22 +00:00
2018-06-26 21:49:33 +00:00
manifestMatches := filterManifests ( mfstList . Manifests , platform )
2017-10-03 23:58:07 +00:00
if len ( manifestMatches ) == 0 {
2019-01-15 17:24:15 +00:00
errMsg := fmt . Sprintf ( "no matching manifest for %s in the manifest list entries" , formatPlatform ( platform ) )
2017-02-10 00:13:57 +00:00
logrus . Debugf ( errMsg )
return "" , "" , errors . New ( errMsg )
2015-12-17 03:19:22 +00:00
}
2017-10-03 23:58:07 +00:00
if len ( manifestMatches ) > 1 {
logrus . Debugf ( "found multiple matches in manifest list, choosing best match %s" , manifestMatches [ 0 ] . Digest . String ( ) )
}
manifestDigest := manifestMatches [ 0 ] . Digest
2018-02-15 21:17:27 +00:00
if err := checkImageCompatibility ( manifestMatches [ 0 ] . Platform . OS , manifestMatches [ 0 ] . Platform . OSVersion ) ; err != nil {
return "" , "" , err
}
2015-12-17 03:19:22 +00:00
manSvc , err := p . repo . Manifests ( ctx )
if err != nil {
return "" , "" , err
}
manifest , err := manSvc . Get ( ctx , manifestDigest )
if err != nil {
return "" , "" , err
}
2016-11-10 23:59:02 +00:00
manifestRef , err := reference . WithDigest ( reference . TrimNamed ( ref ) , manifestDigest )
2015-12-17 03:19:22 +00:00
if err != nil {
return "" , "" , err
}
switch v := manifest . ( type ) {
case * schema1 . SignedManifest :
2019-08-13 18:13:14 +00:00
msg := fmt . Sprintf ( "[DEPRECATION NOTICE] v2 schema1 manifests in manifest lists are not supported and will break in a future release. Suggest author of %s to upgrade to v2 schema2. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/" , ref )
2019-06-15 01:56:28 +00:00
logrus . Warn ( msg )
progress . Message ( p . config . ProgressOutput , "" , msg )
2018-06-26 21:49:33 +00:00
platform := toOCIPlatform ( manifestMatches [ 0 ] . Platform )
id , _ , err = p . pullSchema1 ( ctx , manifestRef , v , & platform )
2015-12-17 03:19:22 +00:00
if err != nil {
return "" , "" , err
}
case * schema2 . DeserializedManifest :
2018-06-26 21:49:33 +00:00
platform := toOCIPlatform ( manifestMatches [ 0 ] . Platform )
id , _ , err = p . pullSchema2 ( ctx , manifestRef , v , & platform )
2015-12-17 03:19:22 +00:00
if err != nil {
return "" , "" , err
}
default :
return "" , "" , errors . New ( "unsupported manifest format" )
}
2016-09-15 23:37:32 +00:00
return id , manifestListDigest , err
2015-12-17 03:19:22 +00:00
}
2016-09-15 23:37:32 +00:00
func ( p * v2Puller ) pullSchema2Config ( ctx context . Context , dgst digest . Digest ) ( configJSON [ ] byte , err error ) {
2015-12-11 23:24:12 +00:00
blobs := p . repo . Blobs ( ctx )
configJSON , err = blobs . Get ( ctx , dgst )
if err != nil {
return nil , err
}
// Verify image config digest
2017-01-07 01:23:18 +00:00
verifier := dgst . Verifier ( )
2015-12-11 23:24:12 +00:00
if _ , err := verifier . Write ( configJSON ) ; err != nil {
return nil , err
}
if ! verifier . Verified ( ) {
err := fmt . Errorf ( "image config verification failed for digest %s" , dgst )
logrus . Error ( err )
return nil , err
2015-11-18 22:18:44 +00:00
}
2015-12-11 23:24:12 +00:00
return configJSON , nil
2015-11-18 22:18:44 +00:00
}
2015-12-17 03:19:22 +00:00
// schema2ManifestDigest computes the manifest digest, and, if pulling by
// digest, ensures that it matches the requested digest.
func schema2ManifestDigest ( ref reference . Named , mfst distribution . Manifest ) ( digest . Digest , error ) {
_ , canonical , err := mfst . Payload ( )
if err != nil {
return "" , err
}
// If pull by digest, then verify the manifest digest.
if digested , isDigested := ref . ( reference . Canonical ) ; isDigested {
2017-01-07 01:23:18 +00:00
verifier := digested . Digest ( ) . Verifier ( )
2015-12-17 03:19:22 +00:00
if _ , err := verifier . Write ( canonical ) ; err != nil {
return "" , err
}
if ! verifier . Verified ( ) {
err := fmt . Errorf ( "manifest verification failed for digest %s" , digested . Digest ( ) )
logrus . Error ( err )
return "" , err
}
return digested . Digest ( ) , nil
}
return digest . FromBytes ( canonical ) , nil
}
2015-12-23 23:21:43 +00:00
// allowV1Fallback checks if the error is a possible reason to fallback to v1
// (even if confirmedV2 has been set already), and if so, wraps the error in
// a fallbackError with confirmedV2 set to false. Otherwise, it returns the
// error unmodified.
func allowV1Fallback ( err error ) error {
switch v := err . ( type ) {
case errcode . Errors :
if len ( v ) != 0 {
2016-02-11 22:08:49 +00:00
if v0 , ok := v [ 0 ] . ( errcode . Error ) ; ok && shouldV2Fallback ( v0 ) {
2016-02-11 23:45:29 +00:00
return fallbackError {
err : err ,
confirmedV2 : false ,
transportOK : true ,
}
2015-12-23 23:21:43 +00:00
}
}
case errcode . Error :
2016-02-11 22:08:49 +00:00
if shouldV2Fallback ( v ) {
2016-02-11 23:45:29 +00:00
return fallbackError {
err : err ,
confirmedV2 : false ,
transportOK : true ,
}
2015-12-23 23:21:43 +00:00
}
2016-02-11 18:30:56 +00:00
case * url . Error :
if v . Err == auth . ErrNoBasicAuthCredentials {
return fallbackError { err : err , confirmedV2 : false }
}
2015-12-23 23:21:43 +00:00
}
return err
}
2017-08-23 22:21:41 +00:00
func verifySchema1Manifest ( signedManifest * schema1 . SignedManifest , ref reference . Reference ) ( m * schema1 . Manifest , err error ) {
2015-11-18 22:18:44 +00:00
// If pull by digest, then verify the manifest digest. NOTE: It is
// important to do this first, before any other content validation. If the
// digest cannot be verified, don't even bother with those other things.
2015-12-04 21:55:15 +00:00
if digested , isCanonical := ref . ( reference . Canonical ) ; isCanonical {
2017-01-07 01:23:18 +00:00
verifier := digested . Digest ( ) . Verifier ( )
2015-12-08 19:14:02 +00:00
if _ , err := verifier . Write ( signedManifest . Canonical ) ; err != nil {
2015-11-18 22:18:44 +00:00
return nil , err
}
if ! verifier . Verified ( ) {
err := fmt . Errorf ( "image verification failed for digest %s" , digested . Digest ( ) )
logrus . Error ( err )
return nil , err
}
}
2015-12-08 19:14:02 +00:00
m = & signedManifest . Manifest
2015-11-18 22:18:44 +00:00
if m . SchemaVersion != 1 {
2017-01-26 00:54:18 +00:00
return nil , fmt . Errorf ( "unsupported schema version %d for %q" , m . SchemaVersion , reference . FamiliarString ( ref ) )
2015-11-18 22:18:44 +00:00
}
if len ( m . FSLayers ) != len ( m . History ) {
2017-01-26 00:54:18 +00:00
return nil , fmt . Errorf ( "length of history not equal to number of layers for %q" , reference . FamiliarString ( ref ) )
2015-11-18 22:18:44 +00:00
}
if len ( m . FSLayers ) == 0 {
2017-01-26 00:54:18 +00:00
return nil , fmt . Errorf ( "no FSLayers in manifest for %q" , reference . FamiliarString ( ref ) )
2015-11-18 22:18:44 +00:00
}
return m , nil
}
// fixManifestLayers removes repeated layers from the manifest and checks the
// correctness of the parent chain.
func fixManifestLayers ( m * schema1 . Manifest ) error {
imgs := make ( [ ] * image . V1Image , len ( m . FSLayers ) )
for i := range m . FSLayers {
img := & image . V1Image { }
if err := json . Unmarshal ( [ ] byte ( m . History [ i ] . V1Compatibility ) , img ) ; err != nil {
return err
}
imgs [ i ] = img
if err := v1 . ValidateID ( img . ID ) ; err != nil {
return err
}
}
if imgs [ len ( imgs ) - 1 ] . Parent != "" && runtime . GOOS != "windows" {
// Windows base layer can point to a base layer parent that is not in manifest.
2016-09-08 08:44:56 +00:00
return errors . New ( "invalid parent ID in the base layer of the image" )
2015-11-18 22:18:44 +00:00
}
// check general duplicates to error instead of a deadlock
idmap := make ( map [ string ] struct { } )
var lastID string
for _ , img := range imgs {
// skip IDs that appear after each other, we handle those later
if _ , exists := idmap [ img . ID ] ; img . ID != lastID && exists {
return fmt . Errorf ( "ID %+v appears multiple times in manifest" , img . ID )
}
lastID = img . ID
idmap [ lastID ] = struct { } { }
}
// backwards loop so that we keep the remaining indexes after removing items
for i := len ( imgs ) - 2 ; i >= 0 ; i -- {
if imgs [ i ] . ID == imgs [ i + 1 ] . ID { // repeated ID. remove and continue
m . FSLayers = append ( m . FSLayers [ : i ] , m . FSLayers [ i + 1 : ] ... )
m . History = append ( m . History [ : i ] , m . History [ i + 1 : ] ... )
} else if imgs [ i ] . Parent != imgs [ i + 1 ] . ID {
2017-08-17 19:16:30 +00:00
return fmt . Errorf ( "invalid parent ID. Expected %v, got %v" , imgs [ i + 1 ] . ID , imgs [ i ] . Parent )
2015-11-18 22:18:44 +00:00
}
}
return nil
}
2016-01-26 02:20:18 +00:00
func createDownloadFile ( ) ( * os . File , error ) {
return ioutil . TempFile ( "" , "GetImageBlob" )
}
2018-06-26 21:49:33 +00:00
func toOCIPlatform ( p manifestlist . PlatformSpec ) specs . Platform {
return specs . Platform {
OS : p . OS ,
Architecture : p . Architecture ,
Variant : p . Variant ,
OSFeatures : p . OSFeatures ,
OSVersion : p . OSVersion ,
}
}