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