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