pull_v2_windows.go 5.2 KB

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