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 }