pull.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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 store meta data about the image (DockerHeaders with prefix X-Meta- in the request).
  14. MetaHeaders map[string][]string
  15. // AuthConfig holds authentication information for authorizing with the registry.
  16. AuthConfig *cliconfig.AuthConfig
  17. // OutStream is the output writer for showing the status of the pull operation.
  18. OutStream io.Writer
  19. }
  20. // Puller is an interface to define Pull behavior.
  21. type Puller interface {
  22. // Pull tries to pull the image referenced by `tag`
  23. // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
  24. //
  25. // TODO(tiborvass): have Pull() take a reference to repository + tag, so that the puller itself is repository-agnostic.
  26. Pull(tag string) (fallback bool, err error)
  27. }
  28. // NewPuller returns a new instance of an implementation conforming to Puller interface.
  29. func NewPuller(s *TagStore, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, sf *streamformatter.StreamFormatter) (Puller, error) {
  30. switch endpoint.Version {
  31. case registry.APIVersion2:
  32. return &v2Puller{
  33. TagStore: s,
  34. endpoint: endpoint,
  35. config: imagePullConfig,
  36. sf: sf,
  37. repoInfo: repoInfo,
  38. }, nil
  39. case registry.APIVersion1:
  40. return &v1Puller{
  41. TagStore: s,
  42. endpoint: endpoint,
  43. config: imagePullConfig,
  44. sf: sf,
  45. repoInfo: repoInfo,
  46. }, nil
  47. }
  48. return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
  49. }
  50. // Pull downloads a image with specified name and tag from the repo.
  51. func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
  52. var sf = streamformatter.NewJSONStreamFormatter()
  53. // Resolve the Repository name from fqn to RepositoryInfo
  54. repoInfo, err := s.registryService.ResolveRepository(image)
  55. if err != nil {
  56. return err
  57. }
  58. // makes sure name is not empty or `scratch`
  59. if err := validateRepoName(repoInfo.LocalName); err != nil {
  60. return err
  61. }
  62. endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
  63. if err != nil {
  64. return err
  65. }
  66. logName := repoInfo.LocalName
  67. if tag != "" {
  68. logName = utils.ImageReference(logName, tag)
  69. }
  70. var (
  71. lastErr error
  72. // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
  73. // By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
  74. // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
  75. // any subsequent ErrNoSupport errors in lastErr.
  76. // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
  77. // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
  78. // error is the ones from v2 endpoints not v1.
  79. discardNoSupportErrors bool
  80. )
  81. for _, endpoint := range endpoints {
  82. logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)
  83. if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
  84. if repoInfo.Official {
  85. s.trustService.UpdateBase()
  86. }
  87. }
  88. puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
  89. if err != nil {
  90. lastErr = err
  91. continue
  92. }
  93. if fallback, err := puller.Pull(tag); err != nil {
  94. if fallback {
  95. if _, ok := err.(registry.ErrNoSupport); !ok {
  96. // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
  97. discardNoSupportErrors = true
  98. // save the current error
  99. lastErr = err
  100. } else if !discardNoSupportErrors {
  101. // Save the ErrNoSupport error, because it's either the first error or all encountered errors
  102. // were also ErrNoSupport errors.
  103. lastErr = err
  104. }
  105. continue
  106. }
  107. logrus.Debugf("Not continuing with error: %v", err)
  108. return err
  109. }
  110. s.eventsService.Log("pull", logName, "")
  111. return nil
  112. }
  113. if lastErr == nil {
  114. lastErr = fmt.Errorf("no endpoints found for %s", image)
  115. }
  116. return lastErr
  117. }
  118. // writeStatus shows status of the pull command.
  119. func writeStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
  120. if layersDownloaded {
  121. out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
  122. } else {
  123. out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
  124. }
  125. }