github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/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/containerd/log" 17 "github.com/docker/distribution" 18 "github.com/docker/distribution/manifest/manifestlist" 19 "github.com/docker/distribution/manifest/schema2" 20 "github.com/docker/distribution/registry/client/transport" 21 "github.com/docker/docker/image" 22 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 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 log.G(ctx).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 log.G(ctx).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 ocispec.Platform) []manifestlist.ManifestDescriptor { 69 version := osversion.Get() 70 osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build) 71 log.G(context.TODO()).Debugf("will prefer Windows entries with version %s", osVersion) 72 73 var matches []manifestlist.ManifestDescriptor 74 foundWindowsMatch := false 75 for _, manifestDescriptor := range manifests { 76 skip := func() { 77 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()) 78 } 79 // TODO(thaJeztah): should we also check for the user-provided architecture (if any)? 80 if manifestDescriptor.Platform.Architecture != runtime.GOARCH { 81 skip() 82 continue 83 } 84 os := manifestDescriptor.Platform.OS 85 if p.OS != "" { 86 // Explicit user request for an OS 87 os = p.OS 88 } 89 if err := image.CheckOS(os); err != nil { 90 skip() 91 continue 92 } 93 // TODO(thaJeztah): should we also take the user-provided platform into account (if any)? 94 if strings.EqualFold("windows", manifestDescriptor.Platform.OS) { 95 if err := checkImageCompatibility("windows", manifestDescriptor.Platform.OSVersion); err != nil { 96 skip() 97 continue 98 } 99 foundWindowsMatch = true 100 } 101 matches = append(matches, manifestDescriptor) 102 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()) 103 } 104 if foundWindowsMatch { 105 sort.Stable(manifestsByVersion{osVersion, matches}) 106 } 107 return matches 108 } 109 110 func versionMatch(actual, expected string) bool { 111 // Check whether the version matches up to the build, ignoring UBR 112 return strings.HasPrefix(actual, expected+".") 113 } 114 115 type manifestsByVersion struct { 116 version string 117 list []manifestlist.ManifestDescriptor 118 } 119 120 func (mbv manifestsByVersion) Less(i, j int) bool { 121 // TODO: Split version by parts and compare 122 // TODO: Prefer versions which have a greater version number 123 // Move compatible versions to the top, with no other ordering changes 124 return (strings.EqualFold("windows", mbv.list[i].Platform.OS) && !strings.EqualFold("windows", mbv.list[j].Platform.OS)) || 125 (versionMatch(mbv.list[i].Platform.OSVersion, mbv.version) && !versionMatch(mbv.list[j].Platform.OSVersion, mbv.version)) 126 } 127 128 func (mbv manifestsByVersion) Len() int { 129 return len(mbv.list) 130 } 131 132 func (mbv manifestsByVersion) Swap(i, j int) { 133 mbv.list[i], mbv.list[j] = mbv.list[j], mbv.list[i] 134 } 135 136 // checkImageCompatibility blocks pulling incompatible images based on a later OS build 137 // Fixes https://github.com/moby/moby/issues/36184. 138 func checkImageCompatibility(imageOS, imageOSVersion string) error { 139 if imageOS == "windows" { 140 hostOSV := osversion.Get() 141 splitImageOSVersion := strings.Split(imageOSVersion, ".") // eg 10.0.16299.nnnn 142 if len(splitImageOSVersion) >= 3 { 143 if imageOSBuild, err := strconv.Atoi(splitImageOSVersion[2]); err == nil { 144 if imageOSBuild > int(hostOSV.Build) { 145 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()) 146 log.G(context.TODO()).Debugf(errMsg) 147 return errors.New(errMsg) 148 } 149 } 150 } 151 } 152 return nil 153 } 154 155 func formatPlatform(platform ocispec.Platform) string { 156 if platform.OS == "" { 157 platform = platforms.DefaultSpec() 158 } 159 return fmt.Sprintf("%s %s", platforms.Format(platform), osversion.Get().ToString()) 160 }