github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/distribution/pull_v2_windows.go (about)

     1  package distribution // import "github.com/docker/docker/distribution"
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"runtime"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/Microsoft/hcsshim/osversion"
    15  	"github.com/containerd/containerd/platforms"
    16  	"github.com/docker/distribution"
    17  	"github.com/docker/distribution/manifest/manifestlist"
    18  	"github.com/docker/distribution/manifest/schema2"
    19  	"github.com/docker/distribution/registry/client/transport"
    20  	"github.com/docker/docker/pkg/system"
    21  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  var _ distribution.Describable = &layerDescriptor{}
    26  
    27  func (ld *layerDescriptor) Descriptor() distribution.Descriptor {
    28  	if ld.src.MediaType == schema2.MediaTypeForeignLayer && len(ld.src.URLs) > 0 {
    29  		return ld.src
    30  	}
    31  	return distribution.Descriptor{}
    32  }
    33  
    34  func (ld *layerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
    35  	blobs := ld.repo.Blobs(ctx)
    36  	rsc, err := blobs.Open(ctx, ld.digest)
    37  
    38  	if len(ld.src.URLs) == 0 {
    39  		return rsc, err
    40  	}
    41  
    42  	// We're done if the registry has this blob.
    43  	if err == nil {
    44  		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
    45  		if _, err = rsc.Seek(0, io.SeekStart); err == nil {
    46  			return rsc, nil
    47  		}
    48  		rsc.Close()
    49  	}
    50  
    51  	// Find the first URL that results in a 200 result code.
    52  	for _, url := range ld.src.URLs {
    53  		logrus.Debugf("Pulling %v from foreign URL %v", ld.digest, url)
    54  		rsc = transport.NewHTTPReadSeeker(http.DefaultClient, url, nil)
    55  
    56  		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
    57  		_, err = rsc.Seek(0, io.SeekStart)
    58  		if err == nil {
    59  			break
    60  		}
    61  		logrus.Debugf("Download for %v failed: %v", ld.digest, err)
    62  		rsc.Close()
    63  		rsc = nil
    64  	}
    65  	return rsc, err
    66  }
    67  
    68  func filterManifests(manifests []manifestlist.ManifestDescriptor, p specs.Platform) []manifestlist.ManifestDescriptor {
    69  	version := osversion.Get()
    70  	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
    71  	logrus.Debugf("will prefer Windows entries with version %s", osVersion)
    72  
    73  	var matches []manifestlist.ManifestDescriptor
    74  	foundWindowsMatch := false
    75  	for _, manifestDescriptor := range manifests {
    76  		if (manifestDescriptor.Platform.Architecture == runtime.GOARCH) &&
    77  			((p.OS != "" && manifestDescriptor.Platform.OS == p.OS) || // Explicit user request for an OS we know we support
    78  				(p.OS == "" && system.IsOSSupported(manifestDescriptor.Platform.OS))) { // No user requested OS, but one we can support
    79  			if strings.EqualFold("windows", manifestDescriptor.Platform.OS) {
    80  				if err := checkImageCompatibility("windows", manifestDescriptor.Platform.OSVersion); err != nil {
    81  					continue
    82  				}
    83  				foundWindowsMatch = true
    84  			}
    85  			matches = append(matches, manifestDescriptor)
    86  			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())
    87  		} else {
    88  			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())
    89  		}
    90  	}
    91  	if foundWindowsMatch {
    92  		sort.Stable(manifestsByVersion{osVersion, matches})
    93  	}
    94  	return matches
    95  }
    96  
    97  func versionMatch(actual, expected string) bool {
    98  	// Check whether the version matches up to the build, ignoring UBR
    99  	return strings.HasPrefix(actual, expected+".")
   100  }
   101  
   102  type manifestsByVersion struct {
   103  	version string
   104  	list    []manifestlist.ManifestDescriptor
   105  }
   106  
   107  func (mbv manifestsByVersion) Less(i, j int) bool {
   108  	// TODO: Split version by parts and compare
   109  	// TODO: Prefer versions which have a greater version number
   110  	// Move compatible versions to the top, with no other ordering changes
   111  	return (strings.EqualFold("windows", mbv.list[i].Platform.OS) && !strings.EqualFold("windows", mbv.list[j].Platform.OS)) ||
   112  		(versionMatch(mbv.list[i].Platform.OSVersion, mbv.version) && !versionMatch(mbv.list[j].Platform.OSVersion, mbv.version))
   113  }
   114  
   115  func (mbv manifestsByVersion) Len() int {
   116  	return len(mbv.list)
   117  }
   118  
   119  func (mbv manifestsByVersion) Swap(i, j int) {
   120  	mbv.list[i], mbv.list[j] = mbv.list[j], mbv.list[i]
   121  }
   122  
   123  // checkImageCompatibility blocks pulling incompatible images based on a later OS build
   124  // Fixes https://github.com/moby/moby/issues/36184.
   125  func checkImageCompatibility(imageOS, imageOSVersion string) error {
   126  	if imageOS == "windows" {
   127  		hostOSV := osversion.Get()
   128  		splitImageOSVersion := strings.Split(imageOSVersion, ".") // eg 10.0.16299.nnnn
   129  		if len(splitImageOSVersion) >= 3 {
   130  			if imageOSBuild, err := strconv.Atoi(splitImageOSVersion[2]); err == nil {
   131  				if imageOSBuild > int(hostOSV.Build) {
   132  					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())
   133  					logrus.Debugf(errMsg)
   134  					return errors.New(errMsg)
   135  				}
   136  			}
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  func formatPlatform(platform specs.Platform) string {
   143  	if platform.OS == "" {
   144  		platform = platforms.DefaultSpec()
   145  	}
   146  	return fmt.Sprintf("%s %s", platforms.Format(platform), osversion.Get().ToString())
   147  }