2018-02-05 16:05:59 -05:00
package distribution // import "github.com/docker/docker/distribution"
2015-11-18 17:18:44 -05:00
import (
2018-04-19 20:00:56 -04:00
"context"
2018-02-15 16:17:27 -05:00
"errors"
2017-10-03 19:58:07 -04:00
"fmt"
2016-05-25 22:11:51 -04:00
"net/http"
"os"
2017-10-03 19:58:07 -04:00
"runtime"
"sort"
2018-02-15 16:17:27 -05:00
"strconv"
2017-10-03 19:58:07 -04:00
"strings"
2015-11-18 17:18:44 -05:00
2019-01-15 12:24:15 -05:00
"github.com/containerd/containerd/platforms"
2016-05-25 22:11:51 -04:00
"github.com/docker/distribution"
2017-10-03 19:58:07 -04:00
"github.com/docker/distribution/manifest/manifestlist"
2016-06-06 20:49:34 -04:00
"github.com/docker/distribution/manifest/schema2"
2016-05-25 22:11:51 -04:00
"github.com/docker/distribution/registry/client/transport"
2017-10-03 19:58:07 -04:00
"github.com/docker/docker/pkg/system"
2018-06-26 17:49:33 -04:00
specs "github.com/opencontainers/image-spec/specs-go/v1"
2017-07-26 17:42:13 -04:00
"github.com/sirupsen/logrus"
2015-11-18 17:18:44 -05:00
)
2016-06-06 20:49:34 -04:00
var _ distribution . Describable = & v2LayerDescriptor { }
2016-05-25 22:11:51 -04:00
2016-06-06 20:49:34 -04:00
func ( ld * v2LayerDescriptor ) Descriptor ( ) distribution . Descriptor {
if ld . src . MediaType == schema2 . MediaTypeForeignLayer && len ( ld . src . URLs ) > 0 {
return ld . src
}
return distribution . Descriptor { }
2016-05-25 22:11:51 -04:00
}
func ( ld * v2LayerDescriptor ) open ( ctx context . Context ) ( distribution . ReadSeekCloser , error ) {
2017-05-09 17:00:31 -04:00
blobs := ld . repo . Blobs ( ctx )
rsc , err := blobs . Open ( ctx , ld . digest )
2016-06-06 20:49:34 -04:00
if len ( ld . src . URLs ) == 0 {
2017-05-09 17:00:31 -04:00
return rsc , err
2016-05-25 22:11:51 -04:00
}
2017-05-09 17:00:31 -04:00
// We're done if the registry has this blob.
if err == nil {
// Seek does an HTTP GET. If it succeeds, the blob really is accessible.
if _ , err = rsc . Seek ( 0 , os . SEEK_SET ) ; err == nil {
return rsc , nil
}
rsc . Close ( )
}
2016-05-25 22:11:51 -04:00
// Find the first URL that results in a 200 result code.
2016-06-06 20:49:34 -04:00
for _ , url := range ld . src . URLs {
2016-09-26 11:47:38 -04:00
logrus . Debugf ( "Pulling %v from foreign URL %v" , ld . digest , url )
2016-05-25 22:11:51 -04:00
rsc = transport . NewHTTPReadSeeker ( http . DefaultClient , url , nil )
2017-05-09 17:00:31 -04:00
// Seek does an HTTP GET. If it succeeds, the blob really is accessible.
2016-05-25 22:11:51 -04:00
_ , err = rsc . Seek ( 0 , os . SEEK_SET )
if err == nil {
break
}
2016-09-26 11:47:38 -04:00
logrus . Debugf ( "Download for %v failed: %v" , ld . digest , err )
2016-05-25 22:11:51 -04:00
rsc . Close ( )
rsc = nil
}
return rsc , err
}
2017-10-03 19:58:07 -04:00
2018-06-26 17:49:33 -04:00
func filterManifests ( manifests [ ] manifestlist . ManifestDescriptor , p specs . Platform ) [ ] manifestlist . ManifestDescriptor {
2018-02-23 18:29:26 -05:00
version := system . GetOSVersion ( )
osVersion := fmt . Sprintf ( "%d.%d.%d" , version . MajorVersion , version . MinorVersion , version . Build )
logrus . Debugf ( "will prefer Windows entries with version %s" , osVersion )
2017-10-03 19:58:07 -04:00
var matches [ ] manifestlist . ManifestDescriptor
2018-02-23 18:29:26 -05:00
foundWindowsMatch := false
2017-10-03 19:58:07 -04:00
for _ , manifestDescriptor := range manifests {
2018-02-23 18:29:26 -05:00
if ( manifestDescriptor . Platform . Architecture == runtime . GOARCH ) &&
2018-06-26 17:49:33 -04:00
( ( p . OS != "" && manifestDescriptor . Platform . OS == p . OS ) || // Explicit user request for an OS we know we support
( p . OS == "" && system . IsOSSupported ( manifestDescriptor . Platform . OS ) ) ) { // No user requested OS, but one we can support
2018-02-23 18:29:26 -05:00
if strings . EqualFold ( "windows" , manifestDescriptor . Platform . OS ) {
2018-06-28 23:30:41 -04:00
if err := checkImageCompatibility ( "windows" , manifestDescriptor . Platform . OSVersion ) ; err != nil {
continue
}
2018-02-23 18:29:26 -05:00
foundWindowsMatch = true
}
2018-06-28 23:30:41 -04:00
matches = append ( matches , manifestDescriptor )
logrus . Debugf ( "found match %s/%s %s with media type %s, digest %s" , manifestDescriptor . Platform . OS , runtime . GOARCH , manifestDescriptor . Platform . OSVersion , manifestDescriptor . MediaType , manifestDescriptor . Digest . String ( ) )
2018-02-15 16:17:27 -05:00
} else {
2018-02-23 18:29:26 -05:00
logrus . Debugf ( "ignoring %s/%s %s with media type %s, digest %s" , manifestDescriptor . Platform . OS , manifestDescriptor . Platform . Architecture , manifestDescriptor . Platform . OSVersion , manifestDescriptor . MediaType , manifestDescriptor . Digest . String ( ) )
2017-10-03 19:58:07 -04:00
}
}
2018-02-23 18:29:26 -05:00
if foundWindowsMatch {
2017-10-07 01:19:06 -04:00
sort . Stable ( manifestsByVersion { osVersion , matches } )
2017-09-13 15:49:04 -04:00
}
2017-10-03 19:58:07 -04:00
return matches
}
func versionMatch ( actual , expected string ) bool {
2017-10-07 01:19:06 -04:00
// Check whether the version matches up to the build, ignoring UBR
return strings . HasPrefix ( actual , expected + "." )
2017-10-03 19:58:07 -04:00
}
2017-10-07 01:19:06 -04:00
type manifestsByVersion struct {
version string
list [ ] manifestlist . ManifestDescriptor
}
2017-10-03 19:58:07 -04:00
func ( mbv manifestsByVersion ) Less ( i , j int ) bool {
// TODO: Split version by parts and compare
// TODO: Prefer versions which have a greater version number
2017-10-07 01:19:06 -04:00
// Move compatible versions to the top, with no other ordering changes
2018-06-28 23:30:41 -04:00
return ( strings . EqualFold ( "windows" , mbv . list [ i ] . Platform . OS ) && ! strings . EqualFold ( "windows" , mbv . list [ j ] . Platform . OS ) ) ||
( versionMatch ( mbv . list [ i ] . Platform . OSVersion , mbv . version ) && ! versionMatch ( mbv . list [ j ] . Platform . OSVersion , mbv . version ) )
2017-10-03 19:58:07 -04:00
}
func ( mbv manifestsByVersion ) Len ( ) int {
2017-10-07 01:19:06 -04:00
return len ( mbv . list )
2017-10-03 19:58:07 -04:00
}
func ( mbv manifestsByVersion ) Swap ( i , j int ) {
2017-10-07 01:19:06 -04:00
mbv . list [ i ] , mbv . list [ j ] = mbv . list [ j ] , mbv . list [ i ]
2017-10-03 19:58:07 -04:00
}
2018-02-15 16:17:27 -05:00
// checkImageCompatibility blocks pulling incompatible images based on a later OS build
// Fixes https://github.com/moby/moby/issues/36184.
func checkImageCompatibility ( imageOS , imageOSVersion string ) error {
if imageOS == "windows" {
hostOSV := system . GetOSVersion ( )
splitImageOSVersion := strings . Split ( imageOSVersion , "." ) // eg 10.0.16299.nnnn
if len ( splitImageOSVersion ) >= 3 {
if imageOSBuild , err := strconv . Atoi ( splitImageOSVersion [ 2 ] ) ; err == nil {
if imageOSBuild > int ( hostOSV . Build ) {
errMsg := fmt . Sprintf ( "a Windows version %s.%s.%s-based image is incompatible with a %s host" , splitImageOSVersion [ 0 ] , splitImageOSVersion [ 1 ] , splitImageOSVersion [ 2 ] , hostOSV . ToString ( ) )
logrus . Debugf ( errMsg )
return errors . New ( errMsg )
}
}
}
}
return nil
}
2019-01-15 12:24:15 -05:00
func formatPlatform ( platform specs . Platform ) string {
if platform . OS == "" {
platform = platforms . DefaultSpec ( )
}
return fmt . Sprintf ( "%s %s" , platforms . Format ( platform ) , system . GetOSVersion ( ) . ToString ( ) )
}