Browse code

Move "pull" and "push" to graph/pull.go and graph/push.go

This is part of the ongoing effort to remove the deprecated server/
package, and generally cleanup and simplify the codebase.

Signed-off-by: Solomon Hykes <solomon@docker.com>

Solomon Hykes authored on 2014/08/08 15:58:58
Showing 9 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package graph
1
+
2
+import "testing"
3
+
4
+func TestPools(t *testing.T) {
5
+	s := &TagStore{
6
+		pullingPool: make(map[string]chan struct{}),
7
+		pushingPool: make(map[string]chan struct{}),
8
+	}
9
+
10
+	if _, err := s.poolAdd("pull", "test1"); err != nil {
11
+		t.Fatal(err)
12
+	}
13
+	if _, err := s.poolAdd("pull", "test2"); err != nil {
14
+		t.Fatal(err)
15
+	}
16
+	if _, err := s.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
17
+		t.Fatalf("Expected `pull test1 is already in progress`")
18
+	}
19
+	if _, err := s.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
20
+		t.Fatalf("Expected `pull test1 is already in progress`")
21
+	}
22
+	if _, err := s.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
23
+		t.Fatalf("Expected `Unknown pool type`")
24
+	}
25
+	if err := s.poolRemove("pull", "test2"); err != nil {
26
+		t.Fatal(err)
27
+	}
28
+	if err := s.poolRemove("pull", "test2"); err != nil {
29
+		t.Fatal(err)
30
+	}
31
+	if err := s.poolRemove("pull", "test1"); err != nil {
32
+		t.Fatal(err)
33
+	}
34
+	if err := s.poolRemove("push", "test1"); err != nil {
35
+		t.Fatal(err)
36
+	}
37
+	if err := s.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
38
+		t.Fatalf("Expected `Unknown pool type`")
39
+	}
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,300 @@
0
+package graph
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"net"
6
+	"net/url"
7
+	"strings"
8
+	"time"
9
+
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/image"
12
+	"github.com/docker/docker/registry"
13
+	"github.com/docker/docker/utils"
14
+)
15
+
16
+func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
17
+	if n := len(job.Args); n != 1 && n != 2 {
18
+		return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
19
+	}
20
+	var (
21
+		localName   = job.Args[0]
22
+		tag         string
23
+		sf          = utils.NewStreamFormatter(job.GetenvBool("json"))
24
+		authConfig  = &registry.AuthConfig{}
25
+		metaHeaders map[string][]string
26
+	)
27
+	if len(job.Args) > 1 {
28
+		tag = job.Args[1]
29
+	}
30
+
31
+	job.GetenvJson("authConfig", authConfig)
32
+	job.GetenvJson("metaHeaders", &metaHeaders)
33
+
34
+	c, err := s.poolAdd("pull", localName+":"+tag)
35
+	if err != nil {
36
+		if c != nil {
37
+			// Another pull of the same repository is already taking place; just wait for it to finish
38
+			job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName))
39
+			<-c
40
+			return engine.StatusOK
41
+		}
42
+		return job.Error(err)
43
+	}
44
+	defer s.poolRemove("pull", localName+":"+tag)
45
+
46
+	// Resolve the Repository name from fqn to endpoint + name
47
+	hostname, remoteName, err := registry.ResolveRepositoryName(localName)
48
+	if err != nil {
49
+		return job.Error(err)
50
+	}
51
+
52
+	endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)
53
+	if err != nil {
54
+		return job.Error(err)
55
+	}
56
+
57
+	r, err := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
58
+	if err != nil {
59
+		return job.Error(err)
60
+	}
61
+
62
+	if endpoint == registry.IndexServerAddress() {
63
+		// If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar"
64
+		localName = remoteName
65
+	}
66
+
67
+	if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil {
68
+		return job.Error(err)
69
+	}
70
+
71
+	return engine.StatusOK
72
+}
73
+
74
+func (s *TagStore) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
75
+	out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
76
+
77
+	repoData, err := r.GetRepositoryData(remoteName)
78
+	if err != nil {
79
+		if strings.Contains(err.Error(), "HTTP code: 404") {
80
+			return fmt.Errorf("Error: image %s not found", remoteName)
81
+		} else {
82
+			// Unexpected HTTP error
83
+			return err
84
+		}
85
+	}
86
+
87
+	utils.Debugf("Retrieving the tag list")
88
+	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
89
+	if err != nil {
90
+		utils.Errorf("%v", err)
91
+		return err
92
+	}
93
+
94
+	for tag, id := range tagsList {
95
+		repoData.ImgList[id] = &registry.ImgData{
96
+			ID:       id,
97
+			Tag:      tag,
98
+			Checksum: "",
99
+		}
100
+	}
101
+
102
+	utils.Debugf("Registering tags")
103
+	// If no tag has been specified, pull them all
104
+	if askedTag == "" {
105
+		for tag, id := range tagsList {
106
+			repoData.ImgList[id].Tag = tag
107
+		}
108
+	} else {
109
+		// Otherwise, check that the tag exists and use only that one
110
+		id, exists := tagsList[askedTag]
111
+		if !exists {
112
+			return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
113
+		}
114
+		repoData.ImgList[id].Tag = askedTag
115
+	}
116
+
117
+	errors := make(chan error)
118
+	for _, image := range repoData.ImgList {
119
+		downloadImage := func(img *registry.ImgData) {
120
+			if askedTag != "" && img.Tag != askedTag {
121
+				utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
122
+				if parallel {
123
+					errors <- nil
124
+				}
125
+				return
126
+			}
127
+
128
+			if img.Tag == "" {
129
+				utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
130
+				if parallel {
131
+					errors <- nil
132
+				}
133
+				return
134
+			}
135
+
136
+			// ensure no two downloads of the same image happen at the same time
137
+			if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil {
138
+				if c != nil {
139
+					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
140
+					<-c
141
+					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil))
142
+				} else {
143
+					utils.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
144
+				}
145
+				if parallel {
146
+					errors <- nil
147
+				}
148
+				return
149
+			}
150
+			defer s.poolRemove("pull", "img:"+img.ID)
151
+
152
+			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
153
+			success := false
154
+			var lastErr error
155
+			for _, ep := range repoData.Endpoints {
156
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
157
+				if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
158
+					// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
159
+					// As the error is also given to the output stream the user will see the error.
160
+					lastErr = err
161
+					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
162
+					continue
163
+				}
164
+				success = true
165
+				break
166
+			}
167
+			if !success {
168
+				err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr)
169
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil))
170
+				if parallel {
171
+					errors <- err
172
+					return
173
+				}
174
+			}
175
+			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil))
176
+
177
+			if parallel {
178
+				errors <- nil
179
+			}
180
+		}
181
+
182
+		if parallel {
183
+			go downloadImage(image)
184
+		} else {
185
+			downloadImage(image)
186
+		}
187
+	}
188
+	if parallel {
189
+		var lastError error
190
+		for i := 0; i < len(repoData.ImgList); i++ {
191
+			if err := <-errors; err != nil {
192
+				lastError = err
193
+			}
194
+		}
195
+		if lastError != nil {
196
+			return lastError
197
+		}
198
+
199
+	}
200
+	for tag, id := range tagsList {
201
+		if askedTag != "" && tag != askedTag {
202
+			continue
203
+		}
204
+		if err := s.Set(localName, tag, id, true); err != nil {
205
+			return err
206
+		}
207
+	}
208
+
209
+	return nil
210
+}
211
+
212
+func (s *TagStore) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
213
+	history, err := r.GetRemoteHistory(imgID, endpoint, token)
214
+	if err != nil {
215
+		return err
216
+	}
217
+	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil))
218
+	// FIXME: Try to stream the images?
219
+	// FIXME: Launch the getRemoteImage() in goroutines
220
+
221
+	for i := len(history) - 1; i >= 0; i-- {
222
+		id := history[i]
223
+
224
+		// ensure no two downloads of the same layer happen at the same time
225
+		if c, err := s.poolAdd("pull", "layer:"+id); err != nil {
226
+			utils.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
227
+			<-c
228
+		}
229
+		defer s.poolRemove("pull", "layer:"+id)
230
+
231
+		if !s.graph.Exists(id) {
232
+			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil))
233
+			var (
234
+				imgJSON []byte
235
+				imgSize int
236
+				err     error
237
+				img     *image.Image
238
+			)
239
+			retries := 5
240
+			for j := 1; j <= retries; j++ {
241
+				imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
242
+				if err != nil && j == retries {
243
+					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
244
+					return err
245
+				} else if err != nil {
246
+					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
247
+					continue
248
+				}
249
+				img, err = image.NewImgJSON(imgJSON)
250
+				if err != nil && j == retries {
251
+					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
252
+					return fmt.Errorf("Failed to parse json: %s", err)
253
+				} else if err != nil {
254
+					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
255
+					continue
256
+				} else {
257
+					break
258
+				}
259
+			}
260
+
261
+			for j := 1; j <= retries; j++ {
262
+				// Get the layer
263
+				status := "Pulling fs layer"
264
+				if j > 1 {
265
+					status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
266
+				}
267
+				out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil))
268
+				layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
269
+				if uerr, ok := err.(*url.Error); ok {
270
+					err = uerr.Err
271
+				}
272
+				if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
273
+					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
274
+					continue
275
+				} else if err != nil {
276
+					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
277
+					return err
278
+				}
279
+				defer layer.Close()
280
+
281
+				err = s.graph.Register(imgJSON,
282
+					utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"),
283
+					img)
284
+				if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
285
+					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
286
+					continue
287
+				} else if err != nil {
288
+					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil))
289
+					return err
290
+				} else {
291
+					break
292
+				}
293
+			}
294
+		}
295
+		out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil))
296
+
297
+	}
298
+	return nil
299
+}
0 300
new file mode 100644
... ...
@@ -0,0 +1,249 @@
0
+package graph
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"io/ioutil"
6
+	"os"
7
+	"path"
8
+
9
+	"github.com/docker/docker/archive"
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/registry"
12
+	"github.com/docker/docker/utils"
13
+)
14
+
15
+// Retrieve the all the images to be uploaded in the correct order
16
+func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
17
+	var (
18
+		imageList   []string
19
+		imagesSeen  map[string]bool     = make(map[string]bool)
20
+		tagsByImage map[string][]string = make(map[string][]string)
21
+	)
22
+
23
+	for tag, id := range localRepo {
24
+		if requestedTag != "" && requestedTag != tag {
25
+			continue
26
+		}
27
+		var imageListForThisTag []string
28
+
29
+		tagsByImage[id] = append(tagsByImage[id], tag)
30
+
31
+		for img, err := s.graph.Get(id); img != nil; img, err = img.GetParent() {
32
+			if err != nil {
33
+				return nil, nil, err
34
+			}
35
+
36
+			if imagesSeen[img.ID] {
37
+				// This image is already on the list, we can ignore it and all its parents
38
+				break
39
+			}
40
+
41
+			imagesSeen[img.ID] = true
42
+			imageListForThisTag = append(imageListForThisTag, img.ID)
43
+		}
44
+
45
+		// reverse the image list for this tag (so the "most"-parent image is first)
46
+		for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
47
+			imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i]
48
+		}
49
+
50
+		// append to main image list
51
+		imageList = append(imageList, imageListForThisTag...)
52
+	}
53
+	if len(imageList) == 0 {
54
+		return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
55
+	}
56
+	utils.Debugf("Image list: %v", imageList)
57
+	utils.Debugf("Tags by image: %v", tagsByImage)
58
+
59
+	return imageList, tagsByImage, nil
60
+}
61
+
62
+func (s *TagStore) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
63
+	out = utils.NewWriteFlusher(out)
64
+	utils.Debugf("Local repo: %s", localRepo)
65
+	imgList, tagsByImage, err := s.getImageList(localRepo, tag)
66
+	if err != nil {
67
+		return err
68
+	}
69
+
70
+	out.Write(sf.FormatStatus("", "Sending image list"))
71
+
72
+	var (
73
+		repoData   *registry.RepositoryData
74
+		imageIndex []*registry.ImgData
75
+	)
76
+
77
+	for _, imgId := range imgList {
78
+		if tags, exists := tagsByImage[imgId]; exists {
79
+			// If an image has tags you must add an entry in the image index
80
+			// for each tag
81
+			for _, tag := range tags {
82
+				imageIndex = append(imageIndex, &registry.ImgData{
83
+					ID:  imgId,
84
+					Tag: tag,
85
+				})
86
+			}
87
+		} else {
88
+			// If the image does not have a tag it still needs to be sent to the
89
+			// registry with an empty tag so that it is accociated with the repository
90
+			imageIndex = append(imageIndex, &registry.ImgData{
91
+				ID:  imgId,
92
+				Tag: "",
93
+			})
94
+
95
+		}
96
+	}
97
+
98
+	utils.Debugf("Preparing to push %s with the following images and tags\n", localRepo)
99
+	for _, data := range imageIndex {
100
+		utils.Debugf("Pushing ID: %s with Tag: %s\n", data.ID, data.Tag)
101
+	}
102
+
103
+	// Register all the images in a repository with the registry
104
+	// If an image is not in this list it will not be associated with the repository
105
+	repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil)
106
+	if err != nil {
107
+		return err
108
+	}
109
+
110
+	nTag := 1
111
+	if tag == "" {
112
+		nTag = len(localRepo)
113
+	}
114
+	for _, ep := range repoData.Endpoints {
115
+		out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag))
116
+
117
+		for _, imgId := range imgList {
118
+			if r.LookupRemoteImage(imgId, ep, repoData.Tokens) {
119
+				out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
120
+			} else {
121
+				if _, err := s.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
122
+					// FIXME: Continue on error?
123
+					return err
124
+				}
125
+			}
126
+
127
+			for _, tag := range tagsByImage[imgId] {
128
+				out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag))
129
+
130
+				if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil {
131
+					return err
132
+				}
133
+			}
134
+		}
135
+	}
136
+
137
+	if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil {
138
+		return err
139
+	}
140
+
141
+	return nil
142
+}
143
+
144
+func (s *TagStore) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
145
+	out = utils.NewWriteFlusher(out)
146
+	jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
147
+	if err != nil {
148
+		return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
149
+	}
150
+	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil))
151
+
152
+	imgData := &registry.ImgData{
153
+		ID: imgID,
154
+	}
155
+
156
+	// Send the json
157
+	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
158
+		if err == registry.ErrAlreadyExists {
159
+			out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
160
+			return "", nil
161
+		}
162
+		return "", err
163
+	}
164
+
165
+	layerData, err := s.graph.TempLayerArchive(imgID, archive.Uncompressed, sf, out)
166
+	if err != nil {
167
+		return "", fmt.Errorf("Failed to generate layer archive: %s", err)
168
+	}
169
+	defer os.RemoveAll(layerData.Name())
170
+
171
+	// Send the layer
172
+	utils.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
173
+
174
+	checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw)
175
+	if err != nil {
176
+		return "", err
177
+	}
178
+	imgData.Checksum = checksum
179
+	imgData.ChecksumPayload = checksumPayload
180
+	// Send the checksum
181
+	if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
182
+		return "", err
183
+	}
184
+
185
+	out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil))
186
+	return imgData.Checksum, nil
187
+}
188
+
189
+// FIXME: Allow to interrupt current push when new push of same image is done.
190
+func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
191
+	if n := len(job.Args); n != 1 {
192
+		return job.Errorf("Usage: %s IMAGE", job.Name)
193
+	}
194
+	var (
195
+		localName   = job.Args[0]
196
+		sf          = utils.NewStreamFormatter(job.GetenvBool("json"))
197
+		authConfig  = &registry.AuthConfig{}
198
+		metaHeaders map[string][]string
199
+	)
200
+
201
+	tag := job.Getenv("tag")
202
+	job.GetenvJson("authConfig", authConfig)
203
+	job.GetenvJson("metaHeaders", &metaHeaders)
204
+	if _, err := s.poolAdd("push", localName); err != nil {
205
+		return job.Error(err)
206
+	}
207
+	defer s.poolRemove("push", localName)
208
+
209
+	// Resolve the Repository name from fqn to endpoint + name
210
+	hostname, remoteName, err := registry.ResolveRepositoryName(localName)
211
+	if err != nil {
212
+		return job.Error(err)
213
+	}
214
+
215
+	endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)
216
+	if err != nil {
217
+		return job.Error(err)
218
+	}
219
+
220
+	img, err := s.graph.Get(localName)
221
+	r, err2 := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
222
+	if err2 != nil {
223
+		return job.Error(err2)
224
+	}
225
+
226
+	if err != nil {
227
+		reposLen := 1
228
+		if tag == "" {
229
+			reposLen = len(s.Repositories[localName])
230
+		}
231
+		job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))
232
+		// If it fails, try to get the repository
233
+		if localRepo, exists := s.Repositories[localName]; exists {
234
+			if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
235
+				return job.Error(err)
236
+			}
237
+			return engine.StatusOK
238
+		}
239
+		return job.Error(err)
240
+	}
241
+
242
+	var token []string
243
+	job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
244
+	if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil {
245
+		return job.Error(err)
246
+	}
247
+	return engine.StatusOK
248
+}
... ...
@@ -23,6 +23,8 @@ func (s *TagStore) Install(eng *engine.Engine) error {
23 23
 		"viz":            s.CmdViz,
24 24
 		"load":           s.CmdLoad,
25 25
 		"import":         s.CmdImport,
26
+		"pull":           s.CmdPull,
27
+		"push":           s.CmdPush,
26 28
 	} {
27 29
 		if err := eng.Register(name, handler); err != nil {
28 30
 			return fmt.Errorf("Could not register %q: %v", name, err)
... ...
@@ -22,6 +22,10 @@ type TagStore struct {
22 22
 	graph        *Graph
23 23
 	Repositories map[string]Repository
24 24
 	sync.Mutex
25
+	// FIXME: move push/pull-related fields
26
+	// to a helper type
27
+	pullingPool map[string]chan struct{}
28
+	pushingPool map[string]chan struct{}
25 29
 }
26 30
 
27 31
 type Repository map[string]string
... ...
@@ -35,6 +39,8 @@ func NewTagStore(path string, graph *Graph) (*TagStore, error) {
35 35
 		path:         abspath,
36 36
 		graph:        graph,
37 37
 		Repositories: make(map[string]Repository),
38
+		pullingPool:  make(map[string]chan struct{}),
39
+		pushingPool:  make(map[string]chan struct{}),
38 40
 	}
39 41
 	// Load the json file if it exists, otherwise create it.
40 42
 	if err := store.reload(); os.IsNotExist(err) {
... ...
@@ -263,3 +269,46 @@ func validateTagName(name string) error {
263 263
 	}
264 264
 	return nil
265 265
 }
266
+
267
+func (s *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
268
+	s.Lock()
269
+	defer s.Unlock()
270
+
271
+	if c, exists := s.pullingPool[key]; exists {
272
+		return c, fmt.Errorf("pull %s is already in progress", key)
273
+	}
274
+	if c, exists := s.pushingPool[key]; exists {
275
+		return c, fmt.Errorf("push %s is already in progress", key)
276
+	}
277
+
278
+	c := make(chan struct{})
279
+	switch kind {
280
+	case "pull":
281
+		s.pullingPool[key] = c
282
+	case "push":
283
+		s.pushingPool[key] = c
284
+	default:
285
+		return nil, fmt.Errorf("Unknown pool type")
286
+	}
287
+	return c, nil
288
+}
289
+
290
+func (s *TagStore) poolRemove(kind, key string) error {
291
+	s.Lock()
292
+	defer s.Unlock()
293
+	switch kind {
294
+	case "pull":
295
+		if c, exists := s.pullingPool[key]; exists {
296
+			close(c)
297
+			delete(s.pullingPool, key)
298
+		}
299
+	case "push":
300
+		if c, exists := s.pushingPool[key]; exists {
301
+			close(c)
302
+			delete(s.pushingPool, key)
303
+		}
304
+	default:
305
+		return fmt.Errorf("Unknown pool type")
306
+	}
307
+	return nil
308
+}
... ...
@@ -5,21 +5,15 @@
5 5
 package server
6 6
 
7 7
 import (
8
-	"fmt"
9 8
 	"io"
10 9
 	"io/ioutil"
11
-	"net"
12
-	"net/url"
13 10
 	"os"
14 11
 	"os/exec"
15
-	"path"
16 12
 	"strings"
17
-	"time"
18 13
 
19 14
 	"github.com/docker/docker/archive"
20 15
 	"github.com/docker/docker/builder"
21 16
 	"github.com/docker/docker/engine"
22
-	"github.com/docker/docker/image"
23 17
 	"github.com/docker/docker/pkg/parsers"
24 18
 	"github.com/docker/docker/registry"
25 19
 	"github.com/docker/docker/utils"
... ...
@@ -104,566 +98,3 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
104 104
 	}
105 105
 	return engine.StatusOK
106 106
 }
107
-
108
-func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
109
-	history, err := r.GetRemoteHistory(imgID, endpoint, token)
110
-	if err != nil {
111
-		return err
112
-	}
113
-	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil))
114
-	// FIXME: Try to stream the images?
115
-	// FIXME: Launch the getRemoteImage() in goroutines
116
-
117
-	for i := len(history) - 1; i >= 0; i-- {
118
-		id := history[i]
119
-
120
-		// ensure no two downloads of the same layer happen at the same time
121
-		if c, err := srv.poolAdd("pull", "layer:"+id); err != nil {
122
-			utils.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
123
-			<-c
124
-		}
125
-		defer srv.poolRemove("pull", "layer:"+id)
126
-
127
-		if !srv.daemon.Graph().Exists(id) {
128
-			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil))
129
-			var (
130
-				imgJSON []byte
131
-				imgSize int
132
-				err     error
133
-				img     *image.Image
134
-			)
135
-			retries := 5
136
-			for j := 1; j <= retries; j++ {
137
-				imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
138
-				if err != nil && j == retries {
139
-					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
140
-					return err
141
-				} else if err != nil {
142
-					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
143
-					continue
144
-				}
145
-				img, err = image.NewImgJSON(imgJSON)
146
-				if err != nil && j == retries {
147
-					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
148
-					return fmt.Errorf("Failed to parse json: %s", err)
149
-				} else if err != nil {
150
-					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
151
-					continue
152
-				} else {
153
-					break
154
-				}
155
-			}
156
-
157
-			for j := 1; j <= retries; j++ {
158
-				// Get the layer
159
-				status := "Pulling fs layer"
160
-				if j > 1 {
161
-					status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
162
-				}
163
-				out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil))
164
-				layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
165
-				if uerr, ok := err.(*url.Error); ok {
166
-					err = uerr.Err
167
-				}
168
-				if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
169
-					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
170
-					continue
171
-				} else if err != nil {
172
-					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
173
-					return err
174
-				}
175
-				defer layer.Close()
176
-
177
-				err = srv.daemon.Graph().Register(imgJSON,
178
-					utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"),
179
-					img)
180
-				if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
181
-					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
182
-					continue
183
-				} else if err != nil {
184
-					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil))
185
-					return err
186
-				} else {
187
-					break
188
-				}
189
-			}
190
-		}
191
-		out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil))
192
-
193
-	}
194
-	return nil
195
-}
196
-
197
-func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
198
-	out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
199
-
200
-	repoData, err := r.GetRepositoryData(remoteName)
201
-	if err != nil {
202
-		if strings.Contains(err.Error(), "HTTP code: 404") {
203
-			return fmt.Errorf("Error: image %s not found", remoteName)
204
-		} else {
205
-			// Unexpected HTTP error
206
-			return err
207
-		}
208
-	}
209
-
210
-	utils.Debugf("Retrieving the tag list")
211
-	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
212
-	if err != nil {
213
-		utils.Errorf("%v", err)
214
-		return err
215
-	}
216
-
217
-	for tag, id := range tagsList {
218
-		repoData.ImgList[id] = &registry.ImgData{
219
-			ID:       id,
220
-			Tag:      tag,
221
-			Checksum: "",
222
-		}
223
-	}
224
-
225
-	utils.Debugf("Registering tags")
226
-	// If no tag has been specified, pull them all
227
-	if askedTag == "" {
228
-		for tag, id := range tagsList {
229
-			repoData.ImgList[id].Tag = tag
230
-		}
231
-	} else {
232
-		// Otherwise, check that the tag exists and use only that one
233
-		id, exists := tagsList[askedTag]
234
-		if !exists {
235
-			return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
236
-		}
237
-		repoData.ImgList[id].Tag = askedTag
238
-	}
239
-
240
-	errors := make(chan error)
241
-	for _, image := range repoData.ImgList {
242
-		downloadImage := func(img *registry.ImgData) {
243
-			if askedTag != "" && img.Tag != askedTag {
244
-				utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
245
-				if parallel {
246
-					errors <- nil
247
-				}
248
-				return
249
-			}
250
-
251
-			if img.Tag == "" {
252
-				utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
253
-				if parallel {
254
-					errors <- nil
255
-				}
256
-				return
257
-			}
258
-
259
-			// ensure no two downloads of the same image happen at the same time
260
-			if c, err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
261
-				if c != nil {
262
-					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
263
-					<-c
264
-					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil))
265
-				} else {
266
-					utils.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
267
-				}
268
-				if parallel {
269
-					errors <- nil
270
-				}
271
-				return
272
-			}
273
-			defer srv.poolRemove("pull", "img:"+img.ID)
274
-
275
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
276
-			success := false
277
-			var lastErr error
278
-			for _, ep := range repoData.Endpoints {
279
-				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
280
-				if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
281
-					// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
282
-					// As the error is also given to the output stream the user will see the error.
283
-					lastErr = err
284
-					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
285
-					continue
286
-				}
287
-				success = true
288
-				break
289
-			}
290
-			if !success {
291
-				err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr)
292
-				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil))
293
-				if parallel {
294
-					errors <- err
295
-					return
296
-				}
297
-			}
298
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil))
299
-
300
-			if parallel {
301
-				errors <- nil
302
-			}
303
-		}
304
-
305
-		if parallel {
306
-			go downloadImage(image)
307
-		} else {
308
-			downloadImage(image)
309
-		}
310
-	}
311
-	if parallel {
312
-		var lastError error
313
-		for i := 0; i < len(repoData.ImgList); i++ {
314
-			if err := <-errors; err != nil {
315
-				lastError = err
316
-			}
317
-		}
318
-		if lastError != nil {
319
-			return lastError
320
-		}
321
-
322
-	}
323
-	for tag, id := range tagsList {
324
-		if askedTag != "" && tag != askedTag {
325
-			continue
326
-		}
327
-		if err := srv.daemon.Repositories().Set(localName, tag, id, true); err != nil {
328
-			return err
329
-		}
330
-	}
331
-
332
-	return nil
333
-}
334
-
335
-func (srv *Server) ImagePull(job *engine.Job) engine.Status {
336
-	if n := len(job.Args); n != 1 && n != 2 {
337
-		return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
338
-	}
339
-	var (
340
-		localName   = job.Args[0]
341
-		tag         string
342
-		sf          = utils.NewStreamFormatter(job.GetenvBool("json"))
343
-		authConfig  = &registry.AuthConfig{}
344
-		metaHeaders map[string][]string
345
-	)
346
-	if len(job.Args) > 1 {
347
-		tag = job.Args[1]
348
-	}
349
-
350
-	job.GetenvJson("authConfig", authConfig)
351
-	job.GetenvJson("metaHeaders", &metaHeaders)
352
-
353
-	c, err := srv.poolAdd("pull", localName+":"+tag)
354
-	if err != nil {
355
-		if c != nil {
356
-			// Another pull of the same repository is already taking place; just wait for it to finish
357
-			job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName))
358
-			<-c
359
-			return engine.StatusOK
360
-		}
361
-		return job.Error(err)
362
-	}
363
-	defer srv.poolRemove("pull", localName+":"+tag)
364
-
365
-	// Resolve the Repository name from fqn to endpoint + name
366
-	hostname, remoteName, err := registry.ResolveRepositoryName(localName)
367
-	if err != nil {
368
-		return job.Error(err)
369
-	}
370
-
371
-	endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)
372
-	if err != nil {
373
-		return job.Error(err)
374
-	}
375
-
376
-	r, err := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
377
-	if err != nil {
378
-		return job.Error(err)
379
-	}
380
-
381
-	if endpoint == registry.IndexServerAddress() {
382
-		// If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar"
383
-		localName = remoteName
384
-	}
385
-
386
-	if err = srv.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil {
387
-		return job.Error(err)
388
-	}
389
-
390
-	return engine.StatusOK
391
-}
392
-
393
-// Retrieve the all the images to be uploaded in the correct order
394
-func (srv *Server) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
395
-	var (
396
-		imageList   []string
397
-		imagesSeen  map[string]bool     = make(map[string]bool)
398
-		tagsByImage map[string][]string = make(map[string][]string)
399
-	)
400
-
401
-	for tag, id := range localRepo {
402
-		if requestedTag != "" && requestedTag != tag {
403
-			continue
404
-		}
405
-		var imageListForThisTag []string
406
-
407
-		tagsByImage[id] = append(tagsByImage[id], tag)
408
-
409
-		for img, err := srv.daemon.Graph().Get(id); img != nil; img, err = img.GetParent() {
410
-			if err != nil {
411
-				return nil, nil, err
412
-			}
413
-
414
-			if imagesSeen[img.ID] {
415
-				// This image is already on the list, we can ignore it and all its parents
416
-				break
417
-			}
418
-
419
-			imagesSeen[img.ID] = true
420
-			imageListForThisTag = append(imageListForThisTag, img.ID)
421
-		}
422
-
423
-		// reverse the image list for this tag (so the "most"-parent image is first)
424
-		for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
425
-			imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i]
426
-		}
427
-
428
-		// append to main image list
429
-		imageList = append(imageList, imageListForThisTag...)
430
-	}
431
-	if len(imageList) == 0 {
432
-		return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
433
-	}
434
-	utils.Debugf("Image list: %v", imageList)
435
-	utils.Debugf("Tags by image: %v", tagsByImage)
436
-
437
-	return imageList, tagsByImage, nil
438
-}
439
-
440
-func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
441
-	out = utils.NewWriteFlusher(out)
442
-	utils.Debugf("Local repo: %s", localRepo)
443
-	imgList, tagsByImage, err := srv.getImageList(localRepo, tag)
444
-	if err != nil {
445
-		return err
446
-	}
447
-
448
-	out.Write(sf.FormatStatus("", "Sending image list"))
449
-
450
-	var (
451
-		repoData   *registry.RepositoryData
452
-		imageIndex []*registry.ImgData
453
-	)
454
-
455
-	for _, imgId := range imgList {
456
-		if tags, exists := tagsByImage[imgId]; exists {
457
-			// If an image has tags you must add an entry in the image index
458
-			// for each tag
459
-			for _, tag := range tags {
460
-				imageIndex = append(imageIndex, &registry.ImgData{
461
-					ID:  imgId,
462
-					Tag: tag,
463
-				})
464
-			}
465
-		} else {
466
-			// If the image does not have a tag it still needs to be sent to the
467
-			// registry with an empty tag so that it is accociated with the repository
468
-			imageIndex = append(imageIndex, &registry.ImgData{
469
-				ID:  imgId,
470
-				Tag: "",
471
-			})
472
-
473
-		}
474
-	}
475
-
476
-	utils.Debugf("Preparing to push %s with the following images and tags\n", localRepo)
477
-	for _, data := range imageIndex {
478
-		utils.Debugf("Pushing ID: %s with Tag: %s\n", data.ID, data.Tag)
479
-	}
480
-
481
-	// Register all the images in a repository with the registry
482
-	// If an image is not in this list it will not be associated with the repository
483
-	repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil)
484
-	if err != nil {
485
-		return err
486
-	}
487
-
488
-	nTag := 1
489
-	if tag == "" {
490
-		nTag = len(localRepo)
491
-	}
492
-	for _, ep := range repoData.Endpoints {
493
-		out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag))
494
-
495
-		for _, imgId := range imgList {
496
-			if r.LookupRemoteImage(imgId, ep, repoData.Tokens) {
497
-				out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
498
-			} else {
499
-				if _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
500
-					// FIXME: Continue on error?
501
-					return err
502
-				}
503
-			}
504
-
505
-			for _, tag := range tagsByImage[imgId] {
506
-				out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag))
507
-
508
-				if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil {
509
-					return err
510
-				}
511
-			}
512
-		}
513
-	}
514
-
515
-	if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil {
516
-		return err
517
-	}
518
-
519
-	return nil
520
-}
521
-
522
-func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
523
-	out = utils.NewWriteFlusher(out)
524
-	jsonRaw, err := ioutil.ReadFile(path.Join(srv.daemon.Graph().Root, imgID, "json"))
525
-	if err != nil {
526
-		return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
527
-	}
528
-	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil))
529
-
530
-	imgData := &registry.ImgData{
531
-		ID: imgID,
532
-	}
533
-
534
-	// Send the json
535
-	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
536
-		if err == registry.ErrAlreadyExists {
537
-			out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
538
-			return "", nil
539
-		}
540
-		return "", err
541
-	}
542
-
543
-	layerData, err := srv.daemon.Graph().TempLayerArchive(imgID, archive.Uncompressed, sf, out)
544
-	if err != nil {
545
-		return "", fmt.Errorf("Failed to generate layer archive: %s", err)
546
-	}
547
-	defer os.RemoveAll(layerData.Name())
548
-
549
-	// Send the layer
550
-	utils.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
551
-
552
-	checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw)
553
-	if err != nil {
554
-		return "", err
555
-	}
556
-	imgData.Checksum = checksum
557
-	imgData.ChecksumPayload = checksumPayload
558
-	// Send the checksum
559
-	if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
560
-		return "", err
561
-	}
562
-
563
-	out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil))
564
-	return imgData.Checksum, nil
565
-}
566
-
567
-// FIXME: Allow to interrupt current push when new push of same image is done.
568
-func (srv *Server) ImagePush(job *engine.Job) engine.Status {
569
-	if n := len(job.Args); n != 1 {
570
-		return job.Errorf("Usage: %s IMAGE", job.Name)
571
-	}
572
-	var (
573
-		localName   = job.Args[0]
574
-		sf          = utils.NewStreamFormatter(job.GetenvBool("json"))
575
-		authConfig  = &registry.AuthConfig{}
576
-		metaHeaders map[string][]string
577
-	)
578
-
579
-	tag := job.Getenv("tag")
580
-	job.GetenvJson("authConfig", authConfig)
581
-	job.GetenvJson("metaHeaders", &metaHeaders)
582
-	if _, err := srv.poolAdd("push", localName); err != nil {
583
-		return job.Error(err)
584
-	}
585
-	defer srv.poolRemove("push", localName)
586
-
587
-	// Resolve the Repository name from fqn to endpoint + name
588
-	hostname, remoteName, err := registry.ResolveRepositoryName(localName)
589
-	if err != nil {
590
-		return job.Error(err)
591
-	}
592
-
593
-	endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)
594
-	if err != nil {
595
-		return job.Error(err)
596
-	}
597
-
598
-	img, err := srv.daemon.Graph().Get(localName)
599
-	r, err2 := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
600
-	if err2 != nil {
601
-		return job.Error(err2)
602
-	}
603
-
604
-	if err != nil {
605
-		reposLen := 1
606
-		if tag == "" {
607
-			reposLen = len(srv.daemon.Repositories().Repositories[localName])
608
-		}
609
-		job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))
610
-		// If it fails, try to get the repository
611
-		if localRepo, exists := srv.daemon.Repositories().Repositories[localName]; exists {
612
-			if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
613
-				return job.Error(err)
614
-			}
615
-			return engine.StatusOK
616
-		}
617
-		return job.Error(err)
618
-	}
619
-
620
-	var token []string
621
-	job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
622
-	if _, err := srv.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil {
623
-		return job.Error(err)
624
-	}
625
-	return engine.StatusOK
626
-}
627
-
628
-func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
629
-	srv.Lock()
630
-	defer srv.Unlock()
631
-
632
-	if c, exists := srv.pullingPool[key]; exists {
633
-		return c, fmt.Errorf("pull %s is already in progress", key)
634
-	}
635
-	if c, exists := srv.pushingPool[key]; exists {
636
-		return c, fmt.Errorf("push %s is already in progress", key)
637
-	}
638
-
639
-	c := make(chan struct{})
640
-	switch kind {
641
-	case "pull":
642
-		srv.pullingPool[key] = c
643
-	case "push":
644
-		srv.pushingPool[key] = c
645
-	default:
646
-		return nil, fmt.Errorf("Unknown pool type")
647
-	}
648
-	return c, nil
649
-}
650
-
651
-func (srv *Server) poolRemove(kind, key string) error {
652
-	srv.Lock()
653
-	defer srv.Unlock()
654
-	switch kind {
655
-	case "pull":
656
-		if c, exists := srv.pullingPool[key]; exists {
657
-			close(c)
658
-			delete(srv.pullingPool, key)
659
-		}
660
-	case "push":
661
-		if c, exists := srv.pushingPool[key]; exists {
662
-			close(c)
663
-			delete(srv.pushingPool, key)
664
-		}
665
-	default:
666
-		return fmt.Errorf("Unknown pool type")
667
-	}
668
-	return nil
669
-}
... ...
@@ -33,8 +33,6 @@ func InitServer(job *engine.Job) engine.Status {
33 33
 
34 34
 	for name, handler := range map[string]engine.Handler{
35 35
 		"build": srv.Build,
36
-		"pull":  srv.ImagePull,
37
-		"push":  srv.ImagePush,
38 36
 	} {
39 37
 		if err := job.Eng.Register(name, srv.handlerWrap(handler)); err != nil {
40 38
 			return job.Error(err)
... ...
@@ -59,10 +57,8 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error)
59 59
 		return nil, err
60 60
 	}
61 61
 	srv := &Server{
62
-		Eng:         eng,
63
-		daemon:      daemon,
64
-		pullingPool: make(map[string]chan struct{}),
65
-		pushingPool: make(map[string]chan struct{}),
62
+		Eng:    eng,
63
+		daemon: daemon,
66 64
 	}
67 65
 	return srv, nil
68 66
 }
... ...
@@ -30,9 +30,7 @@ import (
30 30
 
31 31
 type Server struct {
32 32
 	sync.RWMutex
33
-	daemon      *daemon.Daemon
34
-	pullingPool map[string]chan struct{}
35
-	pushingPool map[string]chan struct{}
36
-	Eng         *engine.Engine
37
-	tasks       sync.WaitGroup
33
+	daemon *daemon.Daemon
34
+	Eng    *engine.Engine
35
+	tasks  sync.WaitGroup
38 36
 }
39 37
deleted file mode 100644
... ...
@@ -1,41 +0,0 @@
1
-package server
2
-
3
-import "testing"
4
-
5
-func TestPools(t *testing.T) {
6
-	srv := &Server{
7
-		pullingPool: make(map[string]chan struct{}),
8
-		pushingPool: make(map[string]chan struct{}),
9
-	}
10
-
11
-	if _, err := srv.poolAdd("pull", "test1"); err != nil {
12
-		t.Fatal(err)
13
-	}
14
-	if _, err := srv.poolAdd("pull", "test2"); err != nil {
15
-		t.Fatal(err)
16
-	}
17
-	if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
18
-		t.Fatalf("Expected `pull test1 is already in progress`")
19
-	}
20
-	if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
21
-		t.Fatalf("Expected `pull test1 is already in progress`")
22
-	}
23
-	if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
24
-		t.Fatalf("Expected `Unknown pool type`")
25
-	}
26
-	if err := srv.poolRemove("pull", "test2"); err != nil {
27
-		t.Fatal(err)
28
-	}
29
-	if err := srv.poolRemove("pull", "test2"); err != nil {
30
-		t.Fatal(err)
31
-	}
32
-	if err := srv.poolRemove("pull", "test1"); err != nil {
33
-		t.Fatal(err)
34
-	}
35
-	if err := srv.poolRemove("push", "test1"); err != nil {
36
-		t.Fatal(err)
37
-	}
38
-	if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
39
-		t.Fatalf("Expected `Unknown pool type`")
40
-	}
41
-}