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