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-20 00:00:56 +00:00
"context"
2018-02-15 21:17:27 +00:00
"errors"
2017-10-03 23:58:07 +00:00
"fmt"
2019-08-06 03:12:23 +00:00
"io"
2016-05-26 02:11:51 +00:00
"net/http"
2017-10-03 23:58:07 +00:00
"runtime"
"sort"
2018-02-15 21:17:27 +00:00
"strconv"
2017-10-03 23:58:07 +00:00
"strings"
2015-11-18 22:18:44 +00:00
2019-04-17 10:39:07 +00:00
"github.com/Microsoft/hcsshim/osversion"
2019-01-15 17:24:15 +00:00
"github.com/containerd/containerd/platforms"
2023-09-13 15:41:45 +00:00
"github.com/containerd/log"
2016-05-26 02:11:51 +00:00
"github.com/docker/distribution"
2017-10-03 23:58:07 +00:00
"github.com/docker/distribution/manifest/manifestlist"
2016-06-07 00:49:34 +00:00
"github.com/docker/distribution/manifest/schema2"
2016-05-26 02:11:51 +00:00
"github.com/docker/distribution/registry/client/transport"
2023-09-06 10:35:50 +00:00
"github.com/docker/docker/image"
2023-05-08 09:57:52 +00:00
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2015-11-18 22:18:44 +00:00
)
2022-02-27 19:57:07 +00:00
var _ distribution . Describable = & layerDescriptor { }
2016-05-26 02:11:51 +00:00
2022-02-27 19:57:07 +00:00
func ( ld * layerDescriptor ) Descriptor ( ) distribution . Descriptor {
2016-06-07 00:49:34 +00:00
if ld . src . MediaType == schema2 . MediaTypeForeignLayer && len ( ld . src . URLs ) > 0 {
return ld . src
}
return distribution . Descriptor { }
2016-05-26 02:11:51 +00:00
}
2022-02-27 19:57:07 +00:00
func ( ld * layerDescriptor ) open ( ctx context . Context ) ( distribution . ReadSeekCloser , error ) {
2017-05-09 21:00:31 +00:00
blobs := ld . repo . Blobs ( ctx )
rsc , err := blobs . Open ( ctx , ld . digest )
2016-06-07 00:49:34 +00:00
if len ( ld . src . URLs ) == 0 {
2017-05-09 21:00:31 +00:00
return rsc , err
2016-05-26 02:11:51 +00:00
}
2017-05-09 21:00:31 +00: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.
2019-08-06 03:12:23 +00:00
if _ , err = rsc . Seek ( 0 , io . SeekStart ) ; err == nil {
2017-05-09 21:00:31 +00:00
return rsc , nil
}
rsc . Close ( )
}
2016-05-26 02:11:51 +00:00
// Find the first URL that results in a 200 result code.
2016-06-07 00:49:34 +00:00
for _ , url := range ld . src . URLs {
2023-06-23 00:33:17 +00:00
log . G ( ctx ) . Debugf ( "Pulling %v from foreign URL %v" , ld . digest , url )
2016-05-26 02:11:51 +00:00
rsc = transport . NewHTTPReadSeeker ( http . DefaultClient , url , nil )
2017-05-09 21:00:31 +00:00
// Seek does an HTTP GET. If it succeeds, the blob really is accessible.
2019-08-06 03:12:23 +00:00
_ , err = rsc . Seek ( 0 , io . SeekStart )
2016-05-26 02:11:51 +00:00
if err == nil {
break
}
2023-06-23 00:33:17 +00:00
log . G ( ctx ) . Debugf ( "Download for %v failed: %v" , ld . digest , err )
2016-05-26 02:11:51 +00:00
rsc . Close ( )
rsc = nil
}
return rsc , err
}
2017-10-03 23:58:07 +00:00
2023-05-08 09:57:52 +00:00
func filterManifests ( manifests [ ] manifestlist . ManifestDescriptor , p ocispec . Platform ) [ ] manifestlist . ManifestDescriptor {
2019-04-17 10:39:07 +00:00
version := osversion . Get ( )
2018-02-23 23:29:26 +00:00
osVersion := fmt . Sprintf ( "%d.%d.%d" , version . MajorVersion , version . MinorVersion , version . Build )
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . Debugf ( "will prefer Windows entries with version %s" , osVersion )
2017-10-03 23:58:07 +00:00
var matches [ ] manifestlist . ManifestDescriptor
2018-02-23 23:29:26 +00:00
foundWindowsMatch := false
2017-10-03 23:58:07 +00:00
for _ , manifestDescriptor := range manifests {
2023-09-06 11:14:22 +00:00
skip := func ( ) {
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . 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 23:58:07 +00:00
}
2023-09-06 11:14:22 +00:00
// TODO(thaJeztah): should we also check for the user-provided architecture (if any)?
if manifestDescriptor . Platform . Architecture != runtime . GOARCH {
skip ( )
continue
}
os := manifestDescriptor . Platform . OS
if p . OS != "" {
// Explicit user request for an OS
os = p . OS
}
2023-09-06 10:35:50 +00:00
if err := image . CheckOS ( os ) ; err != nil {
2023-09-06 11:14:22 +00:00
skip ( )
continue
}
// TODO(thaJeztah): should we also take the user-provided platform into account (if any)?
if strings . EqualFold ( "windows" , manifestDescriptor . Platform . OS ) {
if err := checkImageCompatibility ( "windows" , manifestDescriptor . Platform . OSVersion ) ; err != nil {
skip ( )
continue
}
foundWindowsMatch = true
}
matches = append ( matches , manifestDescriptor )
log . G ( context . TODO ( ) ) . 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 ( ) )
2017-10-03 23:58:07 +00:00
}
2018-02-23 23:29:26 +00:00
if foundWindowsMatch {
2017-10-07 05:19:06 +00:00
sort . Stable ( manifestsByVersion { osVersion , matches } )
2017-09-13 19:49:04 +00:00
}
2017-10-03 23:58:07 +00:00
return matches
}
func versionMatch ( actual , expected string ) bool {
2017-10-07 05:19:06 +00:00
// Check whether the version matches up to the build, ignoring UBR
return strings . HasPrefix ( actual , expected + "." )
2017-10-03 23:58:07 +00:00
}
2017-10-07 05:19:06 +00:00
type manifestsByVersion struct {
version string
list [ ] manifestlist . ManifestDescriptor
}
2017-10-03 23:58:07 +00: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 05:19:06 +00:00
// Move compatible versions to the top, with no other ordering changes
2018-06-29 03:30:41 +00: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 23:58:07 +00:00
}
func ( mbv manifestsByVersion ) Len ( ) int {
2017-10-07 05:19:06 +00:00
return len ( mbv . list )
2017-10-03 23:58:07 +00:00
}
func ( mbv manifestsByVersion ) Swap ( i , j int ) {
2017-10-07 05:19:06 +00:00
mbv . list [ i ] , mbv . list [ j ] = mbv . list [ j ] , mbv . list [ i ]
2017-10-03 23:58:07 +00:00
}
2018-02-15 21:17:27 +00: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" {
2019-04-17 10:39:07 +00:00
hostOSV := osversion . Get ( )
2018-02-15 21:17:27 +00:00
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 ( ) )
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . Debugf ( errMsg )
2018-02-15 21:17:27 +00:00
return errors . New ( errMsg )
}
}
}
}
return nil
}
2019-01-15 17:24:15 +00:00
2023-05-08 09:57:52 +00:00
func formatPlatform ( platform ocispec . Platform ) string {
2019-01-15 17:24:15 +00:00
if platform . OS == "" {
platform = platforms . DefaultSpec ( )
}
2019-04-17 10:39:07 +00:00
return fmt . Sprintf ( "%s %s" , platforms . Format ( platform ) , osversion . Get ( ) . ToString ( ) )
2019-01-15 17:24:15 +00:00
}