pull.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package distribution
  2. import (
  3. "fmt"
  4. "github.com/Sirupsen/logrus"
  5. "github.com/docker/distribution/digest"
  6. "github.com/docker/docker/api"
  7. "github.com/docker/docker/distribution/metadata"
  8. "github.com/docker/docker/distribution/xfer"
  9. "github.com/docker/docker/image"
  10. "github.com/docker/docker/pkg/progress"
  11. "github.com/docker/docker/reference"
  12. "github.com/docker/docker/registry"
  13. "github.com/docker/engine-api/types"
  14. "golang.org/x/net/context"
  15. )
  16. // ImagePullConfig stores pull configuration.
  17. type ImagePullConfig struct {
  18. // MetaHeaders stores HTTP headers with metadata about the image
  19. MetaHeaders map[string][]string
  20. // AuthConfig holds authentication credentials for authenticating with
  21. // the registry.
  22. AuthConfig *types.AuthConfig
  23. // ProgressOutput is the interface for showing the status of the pull
  24. // operation.
  25. ProgressOutput progress.Output
  26. // RegistryService is the registry service to use for TLS configuration
  27. // and endpoint lookup.
  28. RegistryService registry.Service
  29. // ImageEventLogger notifies events for a given image
  30. ImageEventLogger func(id, name, action string)
  31. // MetadataStore is the storage backend for distribution-specific
  32. // metadata.
  33. MetadataStore metadata.Store
  34. // ImageStore manages images.
  35. ImageStore image.Store
  36. // ReferenceStore manages tags.
  37. ReferenceStore reference.Store
  38. // DownloadManager manages concurrent pulls.
  39. DownloadManager *xfer.LayerDownloadManager
  40. }
  41. // Puller is an interface that abstracts pulling for different API versions.
  42. type Puller interface {
  43. // Pull tries to pull the image referenced by `tag`
  44. // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
  45. //
  46. Pull(ctx context.Context, ref reference.Named) error
  47. }
  48. // newPuller returns a Puller interface that will pull from either a v1 or v2
  49. // registry. The endpoint argument contains a Version field that determines
  50. // whether a v1 or v2 puller will be created. The other parameters are passed
  51. // through to the underlying puller implementation for use during the actual
  52. // pull operation.
  53. func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) {
  54. switch endpoint.Version {
  55. case registry.APIVersion2:
  56. return &v2Puller{
  57. V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
  58. endpoint: endpoint,
  59. config: imagePullConfig,
  60. repoInfo: repoInfo,
  61. }, nil
  62. case registry.APIVersion1:
  63. return &v1Puller{
  64. v1IDService: metadata.NewV1IDService(imagePullConfig.MetadataStore),
  65. endpoint: endpoint,
  66. config: imagePullConfig,
  67. repoInfo: repoInfo,
  68. }, nil
  69. }
  70. return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
  71. }
  72. // Pull initiates a pull operation. image is the repository name to pull, and
  73. // tag may be either empty, or indicate a specific tag to pull.
  74. func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
  75. // Resolve the Repository name from fqn to RepositoryInfo
  76. repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
  77. if err != nil {
  78. return err
  79. }
  80. // makes sure name is not empty or `scratch`
  81. if err := ValidateRepoName(repoInfo.Name()); err != nil {
  82. return err
  83. }
  84. endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo.Hostname())
  85. if err != nil {
  86. return err
  87. }
  88. var (
  89. lastErr error
  90. // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
  91. // By default it is false, which means that if an ErrNoSupport error is encountered, it will be saved in lastErr.
  92. // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
  93. // any subsequent ErrNoSupport errors in lastErr.
  94. // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
  95. // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
  96. // error is the ones from v2 endpoints not v1.
  97. discardNoSupportErrors bool
  98. // confirmedV2 is set to true if a pull attempt managed to
  99. // confirm that it was talking to a v2 registry. This will
  100. // prevent fallback to the v1 protocol.
  101. confirmedV2 bool
  102. // confirmedTLSRegistries is a map indicating which registries
  103. // are known to be using TLS. There should never be a plaintext
  104. // retry for any of these.
  105. confirmedTLSRegistries = make(map[string]struct{})
  106. )
  107. for _, endpoint := range endpoints {
  108. if confirmedV2 && endpoint.Version == registry.APIVersion1 {
  109. logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
  110. continue
  111. }
  112. if endpoint.URL.Scheme != "https" {
  113. if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
  114. logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
  115. continue
  116. }
  117. }
  118. logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
  119. puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
  120. if err != nil {
  121. lastErr = err
  122. continue
  123. }
  124. if err := puller.Pull(ctx, ref); err != nil {
  125. // Was this pull cancelled? If so, don't try to fall
  126. // back.
  127. fallback := false
  128. select {
  129. case <-ctx.Done():
  130. default:
  131. if fallbackErr, ok := err.(fallbackError); ok {
  132. fallback = true
  133. confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
  134. if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
  135. confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
  136. }
  137. err = fallbackErr.err
  138. }
  139. }
  140. if fallback {
  141. if _, ok := err.(ErrNoSupport); !ok {
  142. // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
  143. discardNoSupportErrors = true
  144. // append subsequent errors
  145. lastErr = err
  146. } else if !discardNoSupportErrors {
  147. // Save the ErrNoSupport error, because it's either the first error or all encountered errors
  148. // were also ErrNoSupport errors.
  149. // append subsequent errors
  150. lastErr = err
  151. }
  152. logrus.Errorf("Attempting next endpoint for pull after error: %v", err)
  153. continue
  154. }
  155. logrus.Errorf("Not continuing with pull after error: %v", err)
  156. return err
  157. }
  158. imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
  159. return nil
  160. }
  161. if lastErr == nil {
  162. lastErr = fmt.Errorf("no endpoints found for %s", ref.String())
  163. }
  164. return lastErr
  165. }
  166. // writeStatus writes a status message to out. If layersDownloaded is true, the
  167. // status message indicates that a newer image was downloaded. Otherwise, it
  168. // indicates that the image is up to date. requestedTag is the tag the message
  169. // will refer to.
  170. func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) {
  171. if layersDownloaded {
  172. progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag)
  173. } else {
  174. progress.Message(out, "", "Status: Image is up to date for "+requestedTag)
  175. }
  176. }
  177. // ValidateRepoName validates the name of a repository.
  178. func ValidateRepoName(name string) error {
  179. if name == "" {
  180. return fmt.Errorf("Repository name can't be empty")
  181. }
  182. if name == api.NoBaseImageSpecifier {
  183. return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier)
  184. }
  185. return nil
  186. }
  187. func addDigestReference(store reference.Store, ref reference.Named, dgst digest.Digest, imageID image.ID) error {
  188. dgstRef, err := reference.WithDigest(ref, dgst)
  189. if err != nil {
  190. return err
  191. }
  192. if oldTagImageID, err := store.Get(dgstRef); err == nil {
  193. if oldTagImageID != imageID {
  194. // Updating digests not supported by reference store
  195. logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagImageID, imageID)
  196. }
  197. return nil
  198. } else if err != reference.ErrDoesNotExist {
  199. return err
  200. }
  201. return store.AddDigest(dgstRef, imageID, true)
  202. }