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