pull.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package graph
  2. import (
  3. "fmt"
  4. "io"
  5. "github.com/Sirupsen/logrus"
  6. "github.com/docker/docker/cliconfig"
  7. "github.com/docker/docker/pkg/streamformatter"
  8. "github.com/docker/docker/registry"
  9. "github.com/docker/docker/utils"
  10. )
  11. // ImagePullConfig stores pull configuration.
  12. type ImagePullConfig struct {
  13. // MetaHeaders stores HTTP headers with metadata about the image
  14. // (DockerHeaders with prefix X-Meta- in the request).
  15. MetaHeaders map[string][]string
  16. // AuthConfig holds authentication credentials for authenticating with
  17. // the registry.
  18. AuthConfig *cliconfig.AuthConfig
  19. // OutStream is the output writer for showing the status of the pull
  20. // operation.
  21. OutStream io.Writer
  22. }
  23. // Puller is an interface that abstracts pulling for different API versions.
  24. type Puller interface {
  25. // Pull tries to pull the image referenced by `tag`
  26. // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
  27. //
  28. // TODO(tiborvass): have Pull() take a reference to repository + tag, so that the puller itself is repository-agnostic.
  29. Pull(tag string) (fallback bool, err error)
  30. }
  31. // NewPuller returns a Puller interface that will pull from either a v1 or v2
  32. // registry. The endpoint argument contains a Version field that determines
  33. // whether a v1 or v2 puller will be created. The other parameters are passed
  34. // through to the underlying puller implementation for use during the actual
  35. // pull operation.
  36. func NewPuller(s *TagStore, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, sf *streamformatter.StreamFormatter) (Puller, error) {
  37. switch endpoint.Version {
  38. case registry.APIVersion2:
  39. return &v2Puller{
  40. TagStore: s,
  41. endpoint: endpoint,
  42. config: imagePullConfig,
  43. sf: sf,
  44. repoInfo: repoInfo,
  45. }, nil
  46. case registry.APIVersion1:
  47. return &v1Puller{
  48. TagStore: s,
  49. endpoint: endpoint,
  50. config: imagePullConfig,
  51. sf: sf,
  52. repoInfo: repoInfo,
  53. }, nil
  54. }
  55. return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
  56. }
  57. // Pull initiates a pull operation. image is the repository name to pull, and
  58. // tag may be either empty, or indicate a specific tag to pull.
  59. func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
  60. var sf = streamformatter.NewJSONStreamFormatter()
  61. // Resolve the Repository name from fqn to RepositoryInfo
  62. repoInfo, err := s.registryService.ResolveRepository(image)
  63. if err != nil {
  64. return err
  65. }
  66. // makes sure name is not empty or `scratch`
  67. if err := validateRepoName(repoInfo.LocalName); err != nil {
  68. return err
  69. }
  70. endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
  71. if err != nil {
  72. return err
  73. }
  74. logName := repoInfo.LocalName
  75. if tag != "" {
  76. logName = utils.ImageReference(logName, tag)
  77. }
  78. var (
  79. lastErr error
  80. // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
  81. // By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
  82. // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
  83. // any subsequent ErrNoSupport errors in lastErr.
  84. // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
  85. // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
  86. // error is the ones from v2 endpoints not v1.
  87. discardNoSupportErrors bool
  88. )
  89. for _, endpoint := range endpoints {
  90. logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)
  91. if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
  92. if repoInfo.Official {
  93. s.trustService.UpdateBase()
  94. }
  95. }
  96. puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
  97. if err != nil {
  98. lastErr = err
  99. continue
  100. }
  101. if fallback, err := puller.Pull(tag); err != nil {
  102. if fallback {
  103. if _, ok := err.(registry.ErrNoSupport); !ok {
  104. // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
  105. discardNoSupportErrors = true
  106. // save the current error
  107. lastErr = err
  108. } else if !discardNoSupportErrors {
  109. // Save the ErrNoSupport error, because it's either the first error or all encountered errors
  110. // were also ErrNoSupport errors.
  111. lastErr = err
  112. }
  113. continue
  114. }
  115. logrus.Debugf("Not continuing with error: %v", err)
  116. return err
  117. }
  118. s.eventsService.Log("pull", logName, "")
  119. return nil
  120. }
  121. if lastErr == nil {
  122. lastErr = fmt.Errorf("no endpoints found for %s", image)
  123. }
  124. return lastErr
  125. }
  126. // writeStatus writes a status message to out. If layersDownloaded is true, the
  127. // status message indicates that a newer image was downloaded. Otherwise, it
  128. // indicates that the image is up to date. requestedTag is the tag the message
  129. // will refer to.
  130. func writeStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
  131. if layersDownloaded {
  132. out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
  133. } else {
  134. out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
  135. }
  136. }