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