Browse code

Split registry into subpackage

Guillaume J. Charmes authored on 2013/05/15 10:41:39
Showing 9 changed files
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"github.com/dotcloud/docker/auth"
7 7
 	"github.com/dotcloud/docker/utils"
8 8
 	"github.com/gorilla/mux"
9
-	"github.com/shin-/cookiejar"
10 9
 	"io"
11 10
 	"log"
12 11
 	"net/http"
... ...
@@ -73,7 +72,8 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
73 73
 	if err != nil {
74 74
 		return err
75 75
 	} else {
76
-		srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
76
+		// TODO: uncomment this
77
+		//		srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
77 78
 		srv.runtime.authConfig = newAuthConfig
78 79
 	}
79 80
 	if status != "" {
... ...
@@ -333,26 +333,26 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars
333 333
 }
334 334
 
335 335
 func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
336
-	if err := parseForm(r); err != nil {
337
-		return err
338
-	}
339
-
340
-	registry := r.Form.Get("registry")
341
-
342
-	if vars == nil {
343
-		return fmt.Errorf("Missing parameter")
344
-	}
345
-	name := vars["name"]
346
-
347
-	in, out, err := hijackServer(w)
348
-	if err != nil {
349
-		return err
350
-	}
351
-	defer in.Close()
352
-	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
353
-	if err := srv.ImagePush(name, registry, out); err != nil {
354
-		fmt.Fprintf(out, "Error: %s\n", err)
355
-	}
336
+	// if err := parseForm(r); err != nil {
337
+	// 	return err
338
+	// }
339
+
340
+	// registry := r.Form.Get("registry")
341
+
342
+	// if vars == nil {
343
+	// 	return fmt.Errorf("Missing parameter")
344
+	// }
345
+	// name := vars["name"]
346
+
347
+	// in, out, err := hijackServer(w)
348
+	// if err != nil {
349
+	// 	return err
350
+	// }
351
+	// defer in.Close()
352
+	// fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
353
+	// if err := srv.ImagePush(name, registry, out); err != nil {
354
+	// 	fmt.Fprintf(out, "Error: %s\n", err)
355
+	// }
356 356
 	return nil
357 357
 }
358 358
 
... ...
@@ -235,28 +235,29 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
235 235
 			fmt.Fprintf(stdout, "FROM %s\n", arguments)
236 236
 			image, err = builder.runtime.repositories.LookupImage(arguments)
237 237
 			if err != nil {
238
-				if builder.runtime.graph.IsNotExist(err) {
239
-
240
-					var tag, remote string
241
-					if strings.Contains(arguments, ":") {
242
-						remoteParts := strings.Split(arguments, ":")
243
-						tag = remoteParts[1]
244
-						remote = remoteParts[0]
245
-					} else {
246
-						remote = arguments
247
-					}
248
-
249
-					if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
250
-						return nil, err
251
-					}
252
-
253
-					image, err = builder.runtime.repositories.LookupImage(arguments)
254
-					if err != nil {
255
-						return nil, err
256
-					}
257
-				} else {
258
-					return nil, err
259
-				}
238
+				// if builder.runtime.graph.IsNotExist(err) {
239
+
240
+				// 	var tag, remote string
241
+				// 	if strings.Contains(arguments, ":") {
242
+				// 		remoteParts := strings.Split(arguments, ":")
243
+				// 		tag = remoteParts[1]
244
+				// 		remote = remoteParts[0]
245
+				// 	} else {
246
+				// 		remote = arguments
247
+				// 	}
248
+
249
+				// 	panic("TODO: reimplement this")
250
+				// 	// if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
251
+				// 	// 	return nil, err
252
+				// 	// }
253
+
254
+				// 	image, err = builder.runtime.repositories.LookupImage(arguments)
255
+				// 	if err != nil {
256
+				// 		return nil, err
257
+				// 	}
258
+				// } else {
259
+				return nil, err
260
+				// }
260 261
 			}
261 262
 			config = &Config{}
262 263
 
... ...
@@ -3,10 +3,10 @@ package docker
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/registry"
6 7
 	"github.com/dotcloud/docker/utils"
7 8
 	"io"
8 9
 	"io/ioutil"
9
-	"net/http"
10 10
 	"os"
11 11
 	"path"
12 12
 	"path/filepath"
... ...
@@ -19,7 +19,6 @@ import (
19 19
 type Graph struct {
20 20
 	Root         string
21 21
 	idIndex      *utils.TruncIndex
22
-	httpClient   *http.Client
23 22
 	checksumLock map[string]*sync.Mutex
24 23
 	lockSumFile  *sync.Mutex
25 24
 	lockSumMap   *sync.Mutex
... ...
@@ -325,3 +324,17 @@ func (graph *Graph) storeChecksums(checksums map[string]string) error {
325 325
 	}
326 326
 	return nil
327 327
 }
328
+
329
+func (graph *Graph) UpdateChecksuns(newChecksums map[string]*registry.ImgData) error {
330
+	graph.lockSumFile.Lock()
331
+	defer graph.lockSumFile.Unlock()
332
+
333
+	localChecksums, err := graph.getStoredChecksums()
334
+	if err != nil {
335
+		return err
336
+	}
337
+	for id, elem := range newChecksums {
338
+		localChecksums[id] = elem.Checksum
339
+	}
340
+	return graph.storeChecksums(localChecksums)
341
+}
... ...
@@ -360,3 +360,15 @@ func (img *Image) Checksum() (string, error) {
360 360
 
361 361
 	return hash, nil
362 362
 }
363
+
364
+// Build an Image object from raw json data
365
+func NewImgJson(src []byte) (*Image, error) {
366
+	ret := &Image{}
367
+
368
+	utils.Debugf("Json string: {%s}\n", src)
369
+	// FIXME: Is there a cleaner way to "purify" the input json?
370
+	if err := json.Unmarshal(src, ret); err != nil {
371
+		return nil, err
372
+	}
373
+	return ret, nil
374
+}
363 375
deleted file mode 100644
... ...
@@ -1,745 +0,0 @@
1
-package docker
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"github.com/dotcloud/docker/auth"
8
-	"github.com/dotcloud/docker/utils"
9
-	"github.com/shin-/cookiejar"
10
-	"io"
11
-	"io/ioutil"
12
-	"net/http"
13
-	"net/url"
14
-	"os"
15
-	"path"
16
-	"strings"
17
-)
18
-
19
-// Build an Image object from raw json data
20
-func NewImgJson(src []byte) (*Image, error) {
21
-	ret := &Image{}
22
-
23
-	utils.Debugf("Json string: {%s}\n", src)
24
-	// FIXME: Is there a cleaner way to "purify" the input json?
25
-	if err := json.Unmarshal(src, ret); err != nil {
26
-		return nil, err
27
-	}
28
-	return ret, nil
29
-}
30
-
31
-func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
32
-	for _, cookie := range c.Jar.Cookies(req.URL) {
33
-		req.AddCookie(cookie)
34
-	}
35
-	return c.Do(req)
36
-}
37
-
38
-// Retrieve the history of a given image from the Registry.
39
-// Return a list of the parent's json (requested image included)
40
-func (graph *Graph) getRemoteHistory(imgId, registry string, token []string) ([]string, error) {
41
-	client := graph.getHttpClient()
42
-
43
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
44
-	if err != nil {
45
-		return nil, err
46
-	}
47
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
48
-	res, err := client.Do(req)
49
-	if err != nil || res.StatusCode != 200 {
50
-		if res != nil {
51
-			return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
52
-		}
53
-		return nil, err
54
-	}
55
-	defer res.Body.Close()
56
-
57
-	jsonString, err := ioutil.ReadAll(res.Body)
58
-	if err != nil {
59
-		return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
60
-	}
61
-
62
-	utils.Debugf("Ancestry: %s", jsonString)
63
-	history := new([]string)
64
-	if err := json.Unmarshal(jsonString, history); err != nil {
65
-		return nil, err
66
-	}
67
-	return *history, nil
68
-}
69
-
70
-func (graph *Graph) getHttpClient() *http.Client {
71
-	if graph.httpClient == nil {
72
-		graph.httpClient = &http.Client{}
73
-		graph.httpClient.Jar = cookiejar.NewCookieJar()
74
-	}
75
-	return graph.httpClient
76
-}
77
-
78
-// Check if an image exists in the Registry
79
-func (graph *Graph) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
80
-	rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
81
-
82
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
83
-	if err != nil {
84
-		return false
85
-	}
86
-	req.SetBasicAuth(authConfig.Username, authConfig.Password)
87
-	res, err := rt.RoundTrip(req)
88
-	return err == nil && res.StatusCode == 307
89
-}
90
-
91
-func (graph *Graph) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
92
-	u := auth.IndexServerAddress() + "/repositories/" + repository + "/images"
93
-	req, err := http.NewRequest("GET", u, nil)
94
-	if err != nil {
95
-		return nil, err
96
-	}
97
-	if authConfig != nil && len(authConfig.Username) > 0 {
98
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
99
-	}
100
-	res, err := graph.getHttpClient().Do(req)
101
-	if err != nil {
102
-		return nil, err
103
-	}
104
-	defer res.Body.Close()
105
-
106
-	// Repository doesn't exist yet
107
-	if res.StatusCode == 404 {
108
-		return nil, nil
109
-	}
110
-
111
-	jsonData, err := ioutil.ReadAll(res.Body)
112
-	if err != nil {
113
-		return nil, err
114
-	}
115
-
116
-	imageList := []map[string]string{}
117
-
118
-	err = json.Unmarshal(jsonData, &imageList)
119
-	if err != nil {
120
-		utils.Debugf("Body: %s (%s)\n", res.Body, u)
121
-		return nil, err
122
-	}
123
-
124
-	return imageList, nil
125
-}
126
-
127
-// Retrieve an image from the Registry.
128
-// Returns the Image object as well as the layer as an Archive (io.Reader)
129
-func (graph *Graph) getRemoteImage(stdout io.Writer, imgId, registry string, token []string) (*Image, Archive, error) {
130
-	client := graph.getHttpClient()
131
-
132
-	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
133
-	// Get the Json
134
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
135
-	if err != nil {
136
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
137
-	}
138
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
139
-	res, err := client.Do(req)
140
-	if err != nil {
141
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
142
-	}
143
-	if res.StatusCode != 200 {
144
-		return nil, nil, fmt.Errorf("HTTP code %d", res.StatusCode)
145
-	}
146
-	defer res.Body.Close()
147
-
148
-	jsonString, err := ioutil.ReadAll(res.Body)
149
-	if err != nil {
150
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
151
-	}
152
-
153
-	img, err := NewImgJson(jsonString)
154
-	if err != nil {
155
-		return nil, nil, fmt.Errorf("Failed to parse json: %s", err)
156
-	}
157
-	img.Id = imgId
158
-
159
-	// Get the layer
160
-	fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
161
-	req, err = http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
162
-	if err != nil {
163
-		return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
164
-	}
165
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
166
-	res, err = client.Do(req)
167
-	if err != nil {
168
-		return nil, nil, err
169
-	}
170
-	return img, utils.ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil
171
-}
172
-
173
-func (graph *Graph) getRemoteTags(stdout io.Writer, registries []string, repository string, token []string) (map[string]string, error) {
174
-	client := graph.getHttpClient()
175
-	if strings.Count(repository, "/") == 0 {
176
-		// This will be removed once the Registry supports auto-resolution on
177
-		// the "library" namespace
178
-		repository = "library/" + repository
179
-	}
180
-	for _, host := range registries {
181
-		endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
182
-		req, err := http.NewRequest("GET", endpoint, nil)
183
-		if err != nil {
184
-			return nil, err
185
-		}
186
-		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
187
-		res, err := client.Do(req)
188
-		defer res.Body.Close()
189
-		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
190
-		if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
191
-			continue
192
-		} else if res.StatusCode == 404 {
193
-			return nil, fmt.Errorf("Repository not found")
194
-		}
195
-
196
-		result := make(map[string]string)
197
-
198
-		rawJson, err := ioutil.ReadAll(res.Body)
199
-		if err != nil {
200
-			return nil, err
201
-		}
202
-		if err = json.Unmarshal(rawJson, &result); err != nil {
203
-			return nil, err
204
-		}
205
-		return result, nil
206
-	}
207
-	return nil, fmt.Errorf("Could not reach any registry endpoint")
208
-}
209
-
210
-func (graph *Graph) getImageForTag(stdout io.Writer, tag, remote, registry string, token []string) (string, error) {
211
-	client := graph.getHttpClient()
212
-
213
-	if !strings.Contains(remote, "/") {
214
-		remote = "library/" + remote
215
-	}
216
-
217
-	registryEndpoint := "https://" + registry + "/v1"
218
-	repositoryTarget := registryEndpoint + "/repositories/" + remote + "/tags/" + tag
219
-
220
-	req, err := http.NewRequest("GET", repositoryTarget, nil)
221
-	if err != nil {
222
-		return "", err
223
-	}
224
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
225
-	res, err := client.Do(req)
226
-	if err != nil {
227
-		return "", fmt.Errorf("Error while retrieving repository info: %v", err)
228
-	}
229
-	defer res.Body.Close()
230
-	if res.StatusCode == 403 {
231
-		return "", fmt.Errorf("You aren't authorized to access this resource")
232
-	} else if res.StatusCode != 200 {
233
-		return "", fmt.Errorf("HTTP code: %d", res.StatusCode)
234
-	}
235
-
236
-	var imgId string
237
-	rawJson, err := ioutil.ReadAll(res.Body)
238
-	if err != nil {
239
-		return "", err
240
-	}
241
-	if err = json.Unmarshal(rawJson, &imgId); err != nil {
242
-		return "", err
243
-	}
244
-	return imgId, nil
245
-}
246
-
247
-func (graph *Graph) PullImage(stdout io.Writer, imgId, registry string, token []string) error {
248
-	history, err := graph.getRemoteHistory(imgId, registry, token)
249
-	if err != nil {
250
-		return err
251
-	}
252
-	// FIXME: Try to stream the images?
253
-	// FIXME: Launch the getRemoteImage() in goroutines
254
-	for _, id := range history {
255
-		if !graph.Exists(id) {
256
-			img, layer, err := graph.getRemoteImage(stdout, id, registry, token)
257
-			if err != nil {
258
-				// FIXME: Keep goging in case of error?
259
-				return err
260
-			}
261
-			if err = graph.Register(layer, false, img); err != nil {
262
-				return err
263
-			}
264
-		}
265
-	}
266
-	return nil
267
-}
268
-
269
-func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
270
-	client := graph.getHttpClient()
271
-
272
-	fmt.Fprintf(stdout, "Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress())
273
-	repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
274
-
275
-	req, err := http.NewRequest("GET", repositoryTarget, nil)
276
-	if err != nil {
277
-		return err
278
-	}
279
-	if authConfig != nil && len(authConfig.Username) > 0 {
280
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
281
-	}
282
-	req.Header.Set("X-Docker-Token", "true")
283
-
284
-	res, err := client.Do(req)
285
-	if err != nil {
286
-		return err
287
-	}
288
-	defer res.Body.Close()
289
-	if res.StatusCode == 401 {
290
-		return fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
291
-	}
292
-	// TODO: Right now we're ignoring checksums in the response body.
293
-	// In the future, we need to use them to check image validity.
294
-	if res.StatusCode != 200 {
295
-		return fmt.Errorf("HTTP code: %d", res.StatusCode)
296
-	}
297
-
298
-	var token, endpoints []string
299
-	if res.Header.Get("X-Docker-Token") != "" {
300
-		token = res.Header["X-Docker-Token"]
301
-	}
302
-	if res.Header.Get("X-Docker-Endpoints") != "" {
303
-		endpoints = res.Header["X-Docker-Endpoints"]
304
-	} else {
305
-		return fmt.Errorf("Index response didn't contain any endpoints")
306
-	}
307
-
308
-	checksumsJson, err := ioutil.ReadAll(res.Body)
309
-	if err != nil {
310
-		return err
311
-	}
312
-
313
-	// Reload the json file to make sure not to overwrite faster sums
314
-	err = func() error {
315
-		localChecksums := make(map[string]string)
316
-		remoteChecksums := []ImgListJson{}
317
-		checksumDictPth := path.Join(graph.Root, "checksums")
318
-
319
-		if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
320
-			return err
321
-		}
322
-
323
-		graph.lockSumFile.Lock()
324
-		defer graph.lockSumFile.Unlock()
325
-
326
-		if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
327
-			if err := json.Unmarshal(checksumDict, &localChecksums); err != nil {
328
-				return err
329
-			}
330
-		}
331
-
332
-		for _, elem := range remoteChecksums {
333
-			localChecksums[elem.Id] = elem.Checksum
334
-		}
335
-
336
-		checksumsJson, err = json.Marshal(localChecksums)
337
-		if err != nil {
338
-			return err
339
-		}
340
-		if err := ioutil.WriteFile(checksumDictPth, checksumsJson, 0600); err != nil {
341
-			return err
342
-		}
343
-		return nil
344
-	}()
345
-	if err != nil {
346
-		return err
347
-	}
348
-
349
-	var tagsList map[string]string
350
-	if askedTag == "" {
351
-		tagsList, err = graph.getRemoteTags(stdout, endpoints, remote, token)
352
-		if err != nil {
353
-			return err
354
-		}
355
-	} else {
356
-		tagsList = map[string]string{askedTag: ""}
357
-	}
358
-
359
-	for askedTag, imgId := range tagsList {
360
-		fmt.Fprintf(stdout, "Resolving tag \"%s:%s\" from %s\n", remote, askedTag, endpoints)
361
-		success := false
362
-		for _, registry := range endpoints {
363
-			if imgId == "" {
364
-				imgId, err = graph.getImageForTag(stdout, askedTag, remote, registry, token)
365
-				if err != nil {
366
-					fmt.Fprintf(stdout, "Error while retrieving image for tag: %v (%v) ; "+
367
-						"checking next endpoint", askedTag, err)
368
-					continue
369
-				}
370
-			}
371
-
372
-			if err := graph.PullImage(stdout, imgId, "https://"+registry+"/v1", token); err != nil {
373
-				return err
374
-			}
375
-
376
-			if err = repositories.Set(remote, askedTag, imgId, true); err != nil {
377
-				return err
378
-			}
379
-			success = true
380
-		}
381
-
382
-		if !success {
383
-			return fmt.Errorf("Could not find repository on any of the indexed registries.")
384
-		}
385
-	}
386
-
387
-	if err = repositories.Save(); err != nil {
388
-		return err
389
-	}
390
-
391
-	return nil
392
-}
393
-
394
-// Push a local image to the registry
395
-func (graph *Graph) PushImage(stdout io.Writer, img *Image, registry string, token []string) error {
396
-	registry = "https://" + registry + "/v1"
397
-
398
-	client := graph.getHttpClient()
399
-	jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
400
-	if err != nil {
401
-		return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
402
-	}
403
-
404
-	fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
405
-
406
-	// FIXME: try json with UTF8
407
-	jsonData := strings.NewReader(string(jsonRaw))
408
-	req, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/json", jsonData)
409
-	if err != nil {
410
-		return err
411
-	}
412
-	req.Header.Add("Content-type", "application/json")
413
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
414
-
415
-	checksum, err := img.Checksum()
416
-	if err != nil {
417
-		return fmt.Errorf("Error while retrieving checksum for %s: %v", img.Id, err)
418
-	}
419
-	req.Header.Set("X-Docker-Checksum", checksum)
420
-	utils.Debugf("Setting checksum for %s: %s", img.ShortId(), checksum)
421
-	res, err := doWithCookies(client, req)
422
-	if err != nil {
423
-		return fmt.Errorf("Failed to upload metadata: %s", err)
424
-	}
425
-	defer res.Body.Close()
426
-	if len(res.Cookies()) > 0 {
427
-		client.Jar.SetCookies(req.URL, res.Cookies())
428
-	}
429
-	if res.StatusCode != 200 {
430
-		errBody, err := ioutil.ReadAll(res.Body)
431
-		if err != nil {
432
-			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
433
-				" trying to parse response body: %v", res.StatusCode, err)
434
-		}
435
-		var jsonBody map[string]string
436
-		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
437
-			errBody = []byte(err.Error())
438
-		} else if jsonBody["error"] == "Image already exists" {
439
-			fmt.Fprintf(stdout, "Image %v already uploaded ; skipping\n", img.Id)
440
-			return nil
441
-		}
442
-		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
443
-	}
444
-
445
-	fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
446
-	root, err := img.root()
447
-	if err != nil {
448
-		return err
449
-	}
450
-
451
-	var layerData *TempArchive
452
-	// If the archive exists, use it
453
-	file, err := os.Open(layerArchivePath(root))
454
-	if err != nil {
455
-		if os.IsNotExist(err) {
456
-			// If the archive does not exist, create one from the layer
457
-			layerData, err = graph.TempLayerArchive(img.Id, Xz, stdout)
458
-			if err != nil {
459
-				return fmt.Errorf("Failed to generate layer archive: %s", err)
460
-			}
461
-		} else {
462
-			return err
463
-		}
464
-	} else {
465
-		defer file.Close()
466
-		st, err := file.Stat()
467
-		if err != nil {
468
-			return err
469
-		}
470
-		layerData = &TempArchive{file, st.Size()}
471
-	}
472
-
473
-	req3, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/layer", utils.ProgressReader(layerData, int(layerData.Size), stdout, ""))
474
-	if err != nil {
475
-		return err
476
-	}
477
-
478
-	req3.ContentLength = -1
479
-	req3.TransferEncoding = []string{"chunked"}
480
-	req3.Header.Set("Authorization", "Token "+strings.Join(token, ","))
481
-	res3, err := doWithCookies(client, req3)
482
-	if err != nil {
483
-		return fmt.Errorf("Failed to upload layer: %s", err)
484
-	}
485
-	defer res3.Body.Close()
486
-
487
-	if res3.StatusCode != 200 {
488
-		errBody, err := ioutil.ReadAll(res3.Body)
489
-		if err != nil {
490
-			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
491
-				" trying to parse response body: %v", res.StatusCode, err)
492
-		}
493
-		return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res3.StatusCode, errBody)
494
-	}
495
-	return nil
496
-}
497
-
498
-// push a tag on the registry.
499
-// Remote has the format '<user>/<repo>
500
-func (graph *Graph) pushTag(remote, revision, tag, registry string, token []string) error {
501
-	// "jsonify" the string
502
-	revision = "\"" + revision + "\""
503
-	registry = "https://" + registry + "/v1"
504
-
505
-	utils.Debugf("Pushing tags for rev [%s] on {%s}\n", revision, registry+"/users/"+remote+"/"+tag)
506
-
507
-	client := graph.getHttpClient()
508
-	req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
509
-	if err != nil {
510
-		return err
511
-	}
512
-	req.Header.Add("Content-type", "application/json")
513
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
514
-	req.ContentLength = int64(len(revision))
515
-	res, err := doWithCookies(client, req)
516
-	if err != nil {
517
-		return err
518
-	}
519
-	res.Body.Close()
520
-	if res.StatusCode != 200 && res.StatusCode != 201 {
521
-		return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
522
-	}
523
-	return nil
524
-}
525
-
526
-// FIXME: this should really be PushTag
527
-func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId, registry string, token []string) error {
528
-	// Check if the local impage exists
529
-	img, err := graph.Get(imgId)
530
-	if err != nil {
531
-		fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
532
-		return nil
533
-	}
534
-	fmt.Fprintf(stdout, "Pushing image %s:%s\r\n", remote, tag)
535
-	// Push the image
536
-	if err = graph.PushImage(stdout, img, registry, token); err != nil {
537
-		return err
538
-	}
539
-	fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
540
-	// And then the tag
541
-	if err = graph.pushTag(remote, imgId, tag, registry, token); err != nil {
542
-		return err
543
-	}
544
-	return nil
545
-}
546
-
547
-// Retrieve the checksum of an image
548
-// Priority:
549
-// - Check on the stored checksums
550
-// - Check if the archive exists, if it does not, ask the registry
551
-// - If the archive does exists, process the checksum from it
552
-// - If the archive does not exists and not found on registry, process checksum from layer
553
-func (graph *Graph) getChecksum(imageId string) (string, error) {
554
-	// FIXME: Use in-memory map instead of reading the file each time
555
-	if sums, err := graph.getStoredChecksums(); err != nil {
556
-		return "", err
557
-	} else if checksum, exists := sums[imageId]; exists {
558
-		return checksum, nil
559
-	}
560
-
561
-	img, err := graph.Get(imageId)
562
-	if err != nil {
563
-		return "", err
564
-	}
565
-
566
-	if _, err := os.Stat(layerArchivePath(graph.imageRoot(imageId))); err != nil {
567
-		if os.IsNotExist(err) {
568
-			// TODO: Ask the registry for the checksum
569
-			//       As the archive is not there, it is supposed to come from a pull.
570
-		} else {
571
-			return "", err
572
-		}
573
-	}
574
-
575
-	checksum, err := img.Checksum()
576
-	if err != nil {
577
-		return "", err
578
-	}
579
-	return checksum, nil
580
-}
581
-
582
-type ImgListJson struct {
583
-	Id       string `json:"id"`
584
-	Checksum string `json:"checksum,omitempty"`
585
-	tag      string
586
-}
587
-
588
-// Push a repository to the registry.
589
-// Remote has the format '<user>/<repo>
590
-func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
591
-	client := graph.getHttpClient()
592
-	// FIXME: Do not reset the cookie each time? (need to reset it in case updating latest of a repo and repushing)
593
-	client.Jar = cookiejar.NewCookieJar()
594
-	var imgList []*ImgListJson
595
-
596
-	fmt.Fprintf(stdout, "Processing checksums\n")
597
-	imageSet := make(map[string]struct{})
598
-
599
-	for tag, id := range localRepo {
600
-		img, err := graph.Get(id)
601
-		if err != nil {
602
-			return err
603
-		}
604
-		img.WalkHistory(func(img *Image) error {
605
-			if _, exists := imageSet[img.Id]; exists {
606
-				return nil
607
-			}
608
-			imageSet[img.Id] = struct{}{}
609
-			checksum, err := graph.getChecksum(img.Id)
610
-			if err != nil {
611
-				return err
612
-			}
613
-			imgList = append([]*ImgListJson{{
614
-				Id:       img.Id,
615
-				Checksum: checksum,
616
-				tag:      tag,
617
-			}}, imgList...)
618
-			return nil
619
-		})
620
-	}
621
-
622
-	imgListJson, err := json.Marshal(imgList)
623
-	if err != nil {
624
-		return err
625
-	}
626
-
627
-	utils.Debugf("json sent: %s\n", imgListJson)
628
-
629
-	fmt.Fprintf(stdout, "Sending image list\n")
630
-	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
631
-	if err != nil {
632
-		return err
633
-	}
634
-	req.SetBasicAuth(authConfig.Username, authConfig.Password)
635
-	req.ContentLength = int64(len(imgListJson))
636
-	req.Header.Set("X-Docker-Token", "true")
637
-
638
-	res, err := client.Do(req)
639
-	if err != nil {
640
-		return err
641
-	}
642
-	defer res.Body.Close()
643
-
644
-	for res.StatusCode >= 300 && res.StatusCode < 400 {
645
-		utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
646
-		req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
647
-		if err != nil {
648
-			return err
649
-		}
650
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
651
-		req.ContentLength = int64(len(imgListJson))
652
-		req.Header.Set("X-Docker-Token", "true")
653
-
654
-		res, err = client.Do(req)
655
-		if err != nil {
656
-			return err
657
-		}
658
-		defer res.Body.Close()
659
-	}
660
-
661
-	if res.StatusCode != 200 && res.StatusCode != 201 {
662
-		errBody, err := ioutil.ReadAll(res.Body)
663
-		if err != nil {
664
-			return err
665
-		}
666
-		return fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
667
-	}
668
-
669
-	var token, endpoints []string
670
-	if res.Header.Get("X-Docker-Token") != "" {
671
-		token = res.Header["X-Docker-Token"]
672
-		utils.Debugf("Auth token: %v", token)
673
-	} else {
674
-		return fmt.Errorf("Index response didn't contain an access token")
675
-	}
676
-	if res.Header.Get("X-Docker-Endpoints") != "" {
677
-		endpoints = res.Header["X-Docker-Endpoints"]
678
-	} else {
679
-		return fmt.Errorf("Index response didn't contain any endpoints")
680
-	}
681
-
682
-	// FIXME: Send only needed images
683
-	for _, registry := range endpoints {
684
-		fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry, len(localRepo))
685
-		// For each image within the repo, push them
686
-		for _, elem := range imgList {
687
-			if err := graph.pushPrimitive(stdout, remote, elem.tag, elem.Id, registry, token); err != nil {
688
-				// FIXME: Continue on error?
689
-				return err
690
-			}
691
-		}
692
-	}
693
-
694
-	req2, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/images", bytes.NewReader(imgListJson))
695
-	if err != nil {
696
-		return err
697
-	}
698
-	req2.SetBasicAuth(authConfig.Username, authConfig.Password)
699
-	req2.Header["X-Docker-Endpoints"] = endpoints
700
-	req2.ContentLength = int64(len(imgListJson))
701
-	res2, err := client.Do(req2)
702
-	if err != nil {
703
-		return err
704
-	}
705
-	defer res2.Body.Close()
706
-	if res2.StatusCode != 204 {
707
-		if errBody, err := ioutil.ReadAll(res2.Body); err != nil {
708
-			return err
709
-		} else {
710
-			return fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res2.StatusCode, remote, errBody)
711
-		}
712
-	}
713
-
714
-	return nil
715
-}
716
-
717
-type SearchResults struct {
718
-	Query      string              `json:"query"`
719
-	NumResults int                 `json:"num_results"`
720
-	Results    []map[string]string `json:"results"`
721
-}
722
-
723
-func (graph *Graph) SearchRepositories(stdout io.Writer, term string) (*SearchResults, error) {
724
-	client := graph.getHttpClient()
725
-	u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term)
726
-	req, err := http.NewRequest("GET", u, nil)
727
-	if err != nil {
728
-		return nil, err
729
-	}
730
-	res, err := client.Do(req)
731
-	if err != nil {
732
-		return nil, err
733
-	}
734
-	defer res.Body.Close()
735
-	if res.StatusCode != 200 {
736
-		return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
737
-	}
738
-	rawData, err := ioutil.ReadAll(res.Body)
739
-	if err != nil {
740
-		return nil, err
741
-	}
742
-	result := new(SearchResults)
743
-	err = json.Unmarshal(rawData, result)
744
-	return result, err
745
-}
746 1
new file mode 100644
... ...
@@ -0,0 +1,659 @@
0
+package registry
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"github.com/dotcloud/docker/auth"
6
+	"github.com/dotcloud/docker/utils"
7
+	"github.com/shin-/cookiejar"
8
+	"io"
9
+	"io/ioutil"
10
+	"net/http"
11
+	"net/url"
12
+	"strings"
13
+)
14
+
15
+func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
16
+	for _, cookie := range c.Jar.Cookies(req.URL) {
17
+		req.AddCookie(cookie)
18
+	}
19
+	return c.Do(req)
20
+}
21
+
22
+// Retrieve the history of a given image from the Registry.
23
+// Return a list of the parent's json (requested image included)
24
+func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) {
25
+	client := r.getHttpClient()
26
+
27
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
28
+	if err != nil {
29
+		return nil, err
30
+	}
31
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
32
+	res, err := client.Do(req)
33
+	if err != nil || res.StatusCode != 200 {
34
+		if res != nil {
35
+			return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
36
+		}
37
+		return nil, err
38
+	}
39
+	defer res.Body.Close()
40
+
41
+	jsonString, err := ioutil.ReadAll(res.Body)
42
+	if err != nil {
43
+		return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
44
+	}
45
+
46
+	utils.Debugf("Ancestry: %s", jsonString)
47
+	history := new([]string)
48
+	if err := json.Unmarshal(jsonString, history); err != nil {
49
+		return nil, err
50
+	}
51
+	return *history, nil
52
+}
53
+
54
+func (r *Registry) getHttpClient() *http.Client {
55
+	if r.httpClient == nil {
56
+		r.httpClient = &http.Client{}
57
+		r.httpClient.Jar = cookiejar.NewCookieJar()
58
+	}
59
+	return r.httpClient
60
+}
61
+
62
+// Check if an image exists in the Registry
63
+func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
64
+	rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
65
+
66
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
67
+	if err != nil {
68
+		return false
69
+	}
70
+	req.SetBasicAuth(authConfig.Username, authConfig.Password)
71
+	res, err := rt.RoundTrip(req)
72
+	return err == nil && res.StatusCode == 307
73
+}
74
+
75
+func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
76
+	u := auth.IndexServerAddress() + "/repositories/" + repository + "/images"
77
+	req, err := http.NewRequest("GET", u, nil)
78
+	if err != nil {
79
+		return nil, err
80
+	}
81
+	if authConfig != nil && len(authConfig.Username) > 0 {
82
+		req.SetBasicAuth(authConfig.Username, authConfig.Password)
83
+	}
84
+	res, err := r.getHttpClient().Do(req)
85
+	if err != nil {
86
+		return nil, err
87
+	}
88
+	defer res.Body.Close()
89
+
90
+	// Repository doesn't exist yet
91
+	if res.StatusCode == 404 {
92
+		return nil, nil
93
+	}
94
+
95
+	jsonData, err := ioutil.ReadAll(res.Body)
96
+	if err != nil {
97
+		return nil, err
98
+	}
99
+
100
+	imageList := []map[string]string{}
101
+
102
+	err = json.Unmarshal(jsonData, &imageList)
103
+	if err != nil {
104
+		utils.Debugf("Body: %s (%s)\n", res.Body, u)
105
+		return nil, err
106
+	}
107
+
108
+	return imageList, nil
109
+}
110
+
111
+// Retrieve an image from the Registry.
112
+// Returns the Image object as well as the layer as an Archive (io.Reader)
113
+func (r *Registry) GetRemoteImageJson(stdout io.Writer, imgId, registry string, token []string) ([]byte, error) {
114
+	client := r.getHttpClient()
115
+
116
+	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
117
+	// Get the Json
118
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
119
+	if err != nil {
120
+		return nil, fmt.Errorf("Failed to download json: %s", err)
121
+	}
122
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
123
+	res, err := client.Do(req)
124
+	if err != nil {
125
+		return nil, fmt.Errorf("Failed to download json: %s", err)
126
+	}
127
+	defer res.Body.Close()
128
+	if res.StatusCode != 200 {
129
+		return nil, fmt.Errorf("HTTP code %d", res.StatusCode)
130
+	}
131
+	jsonString, err := ioutil.ReadAll(res.Body)
132
+	if err != nil {
133
+		return nil, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
134
+	}
135
+	return jsonString, nil
136
+}
137
+
138
+func (r *Registry) GetRemoteImageLayer(stdout io.Writer, imgId, registry string, token []string) (io.Reader, error) {
139
+	client := r.getHttpClient()
140
+
141
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
142
+	if err != nil {
143
+		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
144
+	}
145
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
146
+	res, err := client.Do(req)
147
+	if err != nil {
148
+		return nil, err
149
+	}
150
+	return utils.ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil
151
+}
152
+
153
+func (r *Registry) GetRemoteTags(stdout io.Writer, registries []string, repository string, token []string) (map[string]string, error) {
154
+	client := r.getHttpClient()
155
+	if strings.Count(repository, "/") == 0 {
156
+		// This will be removed once the Registry supports auto-resolution on
157
+		// the "library" namespace
158
+		repository = "library/" + repository
159
+	}
160
+	for _, host := range registries {
161
+		endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
162
+		req, err := http.NewRequest("GET", endpoint, nil)
163
+		if err != nil {
164
+			return nil, err
165
+		}
166
+		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
167
+		res, err := client.Do(req)
168
+		defer res.Body.Close()
169
+		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
170
+		if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
171
+			continue
172
+		} else if res.StatusCode == 404 {
173
+			return nil, fmt.Errorf("Repository not found")
174
+		}
175
+
176
+		result := make(map[string]string)
177
+
178
+		rawJson, err := ioutil.ReadAll(res.Body)
179
+		if err != nil {
180
+			return nil, err
181
+		}
182
+		if err := json.Unmarshal(rawJson, &result); err != nil {
183
+			return nil, err
184
+		}
185
+		return result, nil
186
+	}
187
+	return nil, fmt.Errorf("Could not reach any registry endpoint")
188
+}
189
+
190
+func (r *Registry) getImageForTag(stdout io.Writer, tag, remote, registry string, token []string) (string, error) {
191
+	client := r.getHttpClient()
192
+
193
+	if !strings.Contains(remote, "/") {
194
+		remote = "library/" + remote
195
+	}
196
+
197
+	registryEndpoint := "https://" + registry + "/v1"
198
+	repositoryTarget := registryEndpoint + "/repositories/" + remote + "/tags/" + tag
199
+
200
+	req, err := http.NewRequest("GET", repositoryTarget, nil)
201
+	if err != nil {
202
+		return "", err
203
+	}
204
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
205
+	res, err := client.Do(req)
206
+	if err != nil {
207
+		return "", fmt.Errorf("Error while retrieving repository info: %v", err)
208
+	}
209
+	defer res.Body.Close()
210
+	if res.StatusCode == 403 {
211
+		return "", fmt.Errorf("You aren't authorized to access this resource")
212
+	} else if res.StatusCode != 200 {
213
+		return "", fmt.Errorf("HTTP code: %d", res.StatusCode)
214
+	}
215
+
216
+	var imgId string
217
+	rawJson, err := ioutil.ReadAll(res.Body)
218
+	if err != nil {
219
+		return "", err
220
+	}
221
+	if err = json.Unmarshal(rawJson, &imgId); err != nil {
222
+		return "", err
223
+	}
224
+	return imgId, nil
225
+}
226
+
227
+func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
228
+	client := r.getHttpClient()
229
+
230
+	utils.Debugf("Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress())
231
+	repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
232
+
233
+	req, err := http.NewRequest("GET", repositoryTarget, nil)
234
+	if err != nil {
235
+		return nil, err
236
+	}
237
+	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
238
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
239
+	}
240
+	req.Header.Set("X-Docker-Token", "true")
241
+
242
+	res, err := client.Do(req)
243
+	if err != nil {
244
+		return nil, err
245
+	}
246
+	defer res.Body.Close()
247
+	if res.StatusCode == 401 {
248
+		return nil, fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
249
+	}
250
+	// TODO: Right now we're ignoring checksums in the response body.
251
+	// In the future, we need to use them to check image validity.
252
+	if res.StatusCode != 200 {
253
+		return nil, fmt.Errorf("HTTP code: %d", res.StatusCode)
254
+	}
255
+
256
+	var tokens []string
257
+	if res.Header.Get("X-Docker-Token") != "" {
258
+		tokens = res.Header["X-Docker-Token"]
259
+	}
260
+
261
+	var endpoints []string
262
+	if res.Header.Get("X-Docker-Endpoints") != "" {
263
+		endpoints = res.Header["X-Docker-Endpoints"]
264
+	} else {
265
+		return nil, fmt.Errorf("Index response didn't contain any endpoints")
266
+	}
267
+
268
+	checksumsJson, err := ioutil.ReadAll(res.Body)
269
+	if err != nil {
270
+		return nil, err
271
+	}
272
+	remoteChecksums := []*ImgData{}
273
+	if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
274
+		return nil, err
275
+	}
276
+
277
+	// Forge a better object from the retrieved data
278
+	imgsData := make(map[string]*ImgData)
279
+	for _, elem := range remoteChecksums {
280
+		imgsData[elem.Id] = elem
281
+	}
282
+
283
+	return &RepositoryData{
284
+		ImgList:   imgsData,
285
+		Endpoints: endpoints,
286
+		Tokens:    tokens,
287
+	}, nil
288
+}
289
+
290
+// // Push a local image to the registry
291
+// func (r *Registry) PushImage(stdout io.Writer, img *Image, registry string, token []string) error {
292
+// 	registry = "https://" + registry + "/v1"
293
+
294
+// 	client := graph.getHttpClient()
295
+// 	jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
296
+// 	if err != nil {
297
+// 		return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
298
+// 	}
299
+
300
+// 	fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
301
+
302
+// 	// FIXME: try json with UTF8
303
+// 	jsonData := strings.NewReader(string(jsonRaw))
304
+// 	req, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/json", jsonData)
305
+// 	if err != nil {
306
+// 		return err
307
+// 	}
308
+// 	req.Header.Add("Content-type", "application/json")
309
+// 	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
310
+
311
+// 	checksum, err := img.Checksum()
312
+// 	if err != nil {
313
+// 		return fmt.Errorf("Error while retrieving checksum for %s: %v", img.Id, err)
314
+// 	}
315
+// 	req.Header.Set("X-Docker-Checksum", checksum)
316
+// 	utils.Debugf("Setting checksum for %s: %s", img.ShortId(), checksum)
317
+// 	res, err := doWithCookies(client, req)
318
+// 	if err != nil {
319
+// 		return fmt.Errorf("Failed to upload metadata: %s", err)
320
+// 	}
321
+// 	defer res.Body.Close()
322
+// 	if len(res.Cookies()) > 0 {
323
+// 		client.Jar.SetCookies(req.URL, res.Cookies())
324
+// 	}
325
+// 	if res.StatusCode != 200 {
326
+// 		errBody, err := ioutil.ReadAll(res.Body)
327
+// 		if err != nil {
328
+// 			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
329
+// 				" trying to parse response body: %v", res.StatusCode, err)
330
+// 		}
331
+// 		var jsonBody map[string]string
332
+// 		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
333
+// 			errBody = []byte(err.Error())
334
+// 		} else if jsonBody["error"] == "Image already exists" {
335
+// 			fmt.Fprintf(stdout, "Image %v already uploaded ; skipping\n", img.Id)
336
+// 			return nil
337
+// 		}
338
+// 		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
339
+// 	}
340
+
341
+// 	fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
342
+// 	root, err := img.root()
343
+// 	if err != nil {
344
+// 		return err
345
+// 	}
346
+
347
+// 	var layerData *TempArchive
348
+// 	// If the archive exists, use it
349
+// 	file, err := os.Open(layerArchivePath(root))
350
+// 	if err != nil {
351
+// 		if os.IsNotExist(err) {
352
+// 			// If the archive does not exist, create one from the layer
353
+// 			layerData, err = graph.TempLayerArchive(img.Id, Xz, stdout)
354
+// 			if err != nil {
355
+// 				return fmt.Errorf("Failed to generate layer archive: %s", err)
356
+// 			}
357
+// 		} else {
358
+// 			return err
359
+// 		}
360
+// 	} else {
361
+// 		defer file.Close()
362
+// 		st, err := file.Stat()
363
+// 		if err != nil {
364
+// 			return err
365
+// 		}
366
+// 		layerData = &TempArchive{file, st.Size()}
367
+// 	}
368
+
369
+// 	req3, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/layer", utils.ProgressReader(layerData, int(layerData.Size), stdout, ""))
370
+// 	if err != nil {
371
+// 		return err
372
+// 	}
373
+
374
+// 	req3.ContentLength = -1
375
+// 	req3.TransferEncoding = []string{"chunked"}
376
+// 	req3.Header.Set("Authorization", "Token "+strings.Join(token, ","))
377
+// 	res3, err := doWithCookies(client, req3)
378
+// 	if err != nil {
379
+// 		return fmt.Errorf("Failed to upload layer: %s", err)
380
+// 	}
381
+// 	defer res3.Body.Close()
382
+
383
+// 	if res3.StatusCode != 200 {
384
+// 		errBody, err := ioutil.ReadAll(res3.Body)
385
+// 		if err != nil {
386
+// 			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
387
+// 				" trying to parse response body: %v", res.StatusCode, err)
388
+// 		}
389
+// 		return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res3.StatusCode, errBody)
390
+// 	}
391
+// 	return nil
392
+// }
393
+
394
+// // push a tag on the registry.
395
+// // Remote has the format '<user>/<repo>
396
+// func (r *Registry) pushTag(remote, revision, tag, registry string, token []string) error {
397
+// 	// "jsonify" the string
398
+// 	revision = "\"" + revision + "\""
399
+// 	registry = "https://" + registry + "/v1"
400
+
401
+// 	utils.Debugf("Pushing tags for rev [%s] on {%s}\n", revision, registry+"/users/"+remote+"/"+tag)
402
+
403
+// 	client := graph.getHttpClient()
404
+// 	req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
405
+// 	if err != nil {
406
+// 		return err
407
+// 	}
408
+// 	req.Header.Add("Content-type", "application/json")
409
+// 	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
410
+// 	req.ContentLength = int64(len(revision))
411
+// 	res, err := doWithCookies(client, req)
412
+// 	if err != nil {
413
+// 		return err
414
+// 	}
415
+// 	res.Body.Close()
416
+// 	if res.StatusCode != 200 && res.StatusCode != 201 {
417
+// 		return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
418
+// 	}
419
+// 	return nil
420
+// }
421
+
422
+// // FIXME: this should really be PushTag
423
+// func (r *Registry) pushPrimitive(stdout io.Writer, remote, tag, imgId, registry string, token []string) error {
424
+// 	// Check if the local impage exists
425
+// 	img, err := graph.Get(imgId)
426
+// 	if err != nil {
427
+// 		fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
428
+// 		return nil
429
+// 	}
430
+// 	fmt.Fprintf(stdout, "Pushing image %s:%s\r\n", remote, tag)
431
+// 	// Push the image
432
+// 	if err = graph.PushImage(stdout, img, registry, token); err != nil {
433
+// 		return err
434
+// 	}
435
+// 	fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
436
+// 	// And then the tag
437
+// 	if err = graph.pushTag(remote, imgId, tag, registry, token); err != nil {
438
+// 		return err
439
+// 	}
440
+// 	return nil
441
+// }
442
+
443
+// // Retrieve the checksum of an image
444
+// // Priority:
445
+// // - Check on the stored checksums
446
+// // - Check if the archive exists, if it does not, ask the registry
447
+// // - If the archive does exists, process the checksum from it
448
+// // - If the archive does not exists and not found on registry, process checksum from layer
449
+// func (r *Registry) getChecksum(imageId string) (string, error) {
450
+// 	// FIXME: Use in-memory map instead of reading the file each time
451
+// 	if sums, err := graph.getStoredChecksums(); err != nil {
452
+// 		return "", err
453
+// 	} else if checksum, exists := sums[imageId]; exists {
454
+// 		return checksum, nil
455
+// 	}
456
+
457
+// 	img, err := graph.Get(imageId)
458
+// 	if err != nil {
459
+// 		return "", err
460
+// 	}
461
+
462
+// 	if _, err := os.Stat(layerArchivePath(graph.imageRoot(imageId))); err != nil {
463
+// 		if os.IsNotExist(err) {
464
+// 			// TODO: Ask the registry for the checksum
465
+// 			//       As the archive is not there, it is supposed to come from a pull.
466
+// 		} else {
467
+// 			return "", err
468
+// 		}
469
+// 	}
470
+
471
+// 	checksum, err := img.Checksum()
472
+// 	if err != nil {
473
+// 		return "", err
474
+// 	}
475
+// 	return checksum, nil
476
+// }
477
+
478
+// // Push a repository to the registry.
479
+// // Remote has the format '<user>/<repo>
480
+// func (r *Registry) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
481
+// 	client := graph.getHttpClient()
482
+// 	// FIXME: Do not reset the cookie each time? (need to reset it in case updating latest of a repo and repushing)
483
+// 	client.Jar = cookiejar.NewCookieJar()
484
+// 	var imgList []*ImgListJson
485
+
486
+// 	fmt.Fprintf(stdout, "Processing checksums\n")
487
+// 	imageSet := make(map[string]struct{})
488
+
489
+// 	for tag, id := range localRepo {
490
+// 		img, err := graph.Get(id)
491
+// 		if err != nil {
492
+// 			return err
493
+// 		}
494
+// 		img.WalkHistory(func(img *Image) error {
495
+// 			if _, exists := imageSet[img.Id]; exists {
496
+// 				return nil
497
+// 			}
498
+// 			imageSet[img.Id] = struct{}{}
499
+// 			checksum, err := graph.getChecksum(img.Id)
500
+// 			if err != nil {
501
+// 				return err
502
+// 			}
503
+// 			imgList = append([]*ImgListJson{{
504
+// 				Id:       img.Id,
505
+// 				Checksum: checksum,
506
+// 				tag:      tag,
507
+// 			}}, imgList...)
508
+// 			return nil
509
+// 		})
510
+// 	}
511
+
512
+// 	imgListJson, err := json.Marshal(imgList)
513
+// 	if err != nil {
514
+// 		return err
515
+// 	}
516
+
517
+// 	utils.Debugf("json sent: %s\n", imgListJson)
518
+
519
+// 	fmt.Fprintf(stdout, "Sending image list\n")
520
+// 	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
521
+// 	if err != nil {
522
+// 		return err
523
+// 	}
524
+// 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
525
+// 	req.ContentLength = int64(len(imgListJson))
526
+// 	req.Header.Set("X-Docker-Token", "true")
527
+
528
+// 	res, err := client.Do(req)
529
+// 	if err != nil {
530
+// 		return err
531
+// 	}
532
+// 	defer res.Body.Close()
533
+
534
+// 	for res.StatusCode >= 300 && res.StatusCode < 400 {
535
+// 		utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
536
+// 		req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
537
+// 		if err != nil {
538
+// 			return err
539
+// 		}
540
+// 		req.SetBasicAuth(authConfig.Username, authConfig.Password)
541
+// 		req.ContentLength = int64(len(imgListJson))
542
+// 		req.Header.Set("X-Docker-Token", "true")
543
+
544
+// 		res, err = client.Do(req)
545
+// 		if err != nil {
546
+// 			return err
547
+// 		}
548
+// 		defer res.Body.Close()
549
+// 	}
550
+
551
+// 	if res.StatusCode != 200 && res.StatusCode != 201 {
552
+// 		errBody, err := ioutil.ReadAll(res.Body)
553
+// 		if err != nil {
554
+// 			return err
555
+// 		}
556
+// 		return fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
557
+// 	}
558
+
559
+// 	var token, endpoints []string
560
+// 	if res.Header.Get("X-Docker-Token") != "" {
561
+// 		token = res.Header["X-Docker-Token"]
562
+// 		utils.Debugf("Auth token: %v", token)
563
+// 	} else {
564
+// 		return fmt.Errorf("Index response didn't contain an access token")
565
+// 	}
566
+// 	if res.Header.Get("X-Docker-Endpoints") != "" {
567
+// 		endpoints = res.Header["X-Docker-Endpoints"]
568
+// 	} else {
569
+// 		return fmt.Errorf("Index response didn't contain any endpoints")
570
+// 	}
571
+
572
+// 	// FIXME: Send only needed images
573
+// 	for _, registry := range endpoints {
574
+// 		fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry, len(localRepo))
575
+// 		// For each image within the repo, push them
576
+// 		for _, elem := range imgList {
577
+// 			if err := graph.pushPrimitive(stdout, remote, elem.tag, elem.Id, registry, token); err != nil {
578
+// 				// FIXME: Continue on error?
579
+// 				return err
580
+// 			}
581
+// 		}
582
+// 	}
583
+
584
+// 	req2, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/images", bytes.NewReader(imgListJson))
585
+// 	if err != nil {
586
+// 		return err
587
+// 	}
588
+// 	req2.SetBasicAuth(authConfig.Username, authConfig.Password)
589
+// 	req2.Header["X-Docker-Endpoints"] = endpoints
590
+// 	req2.ContentLength = int64(len(imgListJson))
591
+// 	res2, err := client.Do(req2)
592
+// 	if err != nil {
593
+// 		return err
594
+// 	}
595
+// 	defer res2.Body.Close()
596
+// 	if res2.StatusCode != 204 {
597
+// 		if errBody, err := ioutil.ReadAll(res2.Body); err != nil {
598
+// 			return err
599
+// 		} else {
600
+// 			return fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res2.StatusCode, remote, errBody)
601
+// 		}
602
+// 	}
603
+
604
+// 	return nil
605
+// }
606
+
607
+func (r *Registry) SearchRepositories(stdout io.Writer, term string) (*SearchResults, error) {
608
+	client := r.getHttpClient()
609
+	u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term)
610
+	req, err := http.NewRequest("GET", u, nil)
611
+	if err != nil {
612
+		return nil, err
613
+	}
614
+	res, err := client.Do(req)
615
+	if err != nil {
616
+		return nil, err
617
+	}
618
+	defer res.Body.Close()
619
+	if res.StatusCode != 200 {
620
+		return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
621
+	}
622
+	rawData, err := ioutil.ReadAll(res.Body)
623
+	if err != nil {
624
+		return nil, err
625
+	}
626
+	result := new(SearchResults)
627
+	err = json.Unmarshal(rawData, result)
628
+	return result, err
629
+}
630
+
631
+type SearchResults struct {
632
+	Query      string              `json:"query"`
633
+	NumResults int                 `json:"num_results"`
634
+	Results    []map[string]string `json:"results"`
635
+}
636
+
637
+type RepositoryData struct {
638
+	ImgList   map[string]*ImgData
639
+	Endpoints []string
640
+	Tokens    []string
641
+}
642
+
643
+type ImgData struct {
644
+	Id       string `json:"id"`
645
+	Checksum string `json:"checksum,omitempty"`
646
+	Tag      string `json:",omitempty"`
647
+}
648
+
649
+type Registry struct {
650
+	httpClient *http.Client
651
+	authConfig *auth.AuthConfig
652
+}
653
+
654
+func NewRegistry(authConfig *auth.AuthConfig) *Registry {
655
+	return &Registry{
656
+		authConfig: authConfig,
657
+	}
658
+}
0 659
new file mode 100644
... ...
@@ -0,0 +1,151 @@
0
+package registry
1
+
2
+import (
3
+	"crypto/rand"
4
+	"encoding/hex"
5
+	"github.com/dotcloud/docker/auth"
6
+	"io/ioutil"
7
+	"os"
8
+	"path"
9
+	"testing"
10
+)
11
+
12
+func TestPull(t *testing.T) {
13
+	os.Setenv("DOCKER_INDEX_URL", "")
14
+	runtime, err := newTestRuntime()
15
+	if err != nil {
16
+		t.Fatal(err)
17
+	}
18
+	defer nuke(runtime)
19
+
20
+	err = runtime.graph.PullRepository(ioutil.Discard, "busybox", "", runtime.repositories, nil)
21
+	if err != nil {
22
+		t.Fatal(err)
23
+	}
24
+	img, err := runtime.repositories.LookupImage("busybox")
25
+	if err != nil {
26
+		t.Fatal(err)
27
+	}
28
+
29
+	// Try to run something on this image to make sure the layer's been downloaded properly.
30
+	config, _, err := ParseRun([]string{img.Id, "echo", "Hello World"}, runtime.capabilities)
31
+	if err != nil {
32
+		t.Fatal(err)
33
+	}
34
+
35
+	b := NewBuilder(runtime)
36
+	container, err := b.Create(config)
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+	if err := container.Start(); err != nil {
41
+		t.Fatal(err)
42
+	}
43
+
44
+	if status := container.Wait(); status != 0 {
45
+		t.Fatalf("Expected status code 0, found %d instead", status)
46
+	}
47
+}
48
+
49
+func TestPullTag(t *testing.T) {
50
+	os.Setenv("DOCKER_INDEX_URL", "")
51
+	runtime, err := newTestRuntime()
52
+	if err != nil {
53
+		t.Fatal(err)
54
+	}
55
+	defer nuke(runtime)
56
+
57
+	err = runtime.graph.PullRepository(ioutil.Discard, "ubuntu", "12.04", runtime.repositories, nil)
58
+	if err != nil {
59
+		t.Fatal(err)
60
+	}
61
+	_, err = runtime.repositories.LookupImage("ubuntu:12.04")
62
+	if err != nil {
63
+		t.Fatal(err)
64
+	}
65
+
66
+	img2, err := runtime.repositories.LookupImage("ubuntu:12.10")
67
+	if img2 != nil {
68
+		t.Fatalf("Expected nil image but found %v instead", img2.Id)
69
+	}
70
+}
71
+
72
+func login(runtime *Runtime) error {
73
+	authConfig := auth.NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", runtime.root)
74
+	runtime.authConfig = authConfig
75
+	_, err := auth.Login(authConfig)
76
+	return err
77
+}
78
+
79
+func TestPush(t *testing.T) {
80
+	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
81
+	defer os.Setenv("DOCKER_INDEX_URL", "")
82
+	runtime, err := newTestRuntime()
83
+	if err != nil {
84
+		t.Fatal(err)
85
+	}
86
+	defer nuke(runtime)
87
+
88
+	err = login(runtime)
89
+	if err != nil {
90
+		t.Fatal(err)
91
+	}
92
+
93
+	err = runtime.graph.PullRepository(ioutil.Discard, "joffrey/busybox", "", runtime.repositories, nil)
94
+	if err != nil {
95
+		t.Fatal(err)
96
+	}
97
+	tokenBuffer := make([]byte, 16)
98
+	_, err = rand.Read(tokenBuffer)
99
+	if err != nil {
100
+		t.Fatal(err)
101
+	}
102
+	token := hex.EncodeToString(tokenBuffer)[:29]
103
+	config, _, err := ParseRun([]string{"joffrey/busybox", "touch", "/" + token}, runtime.capabilities)
104
+	if err != nil {
105
+		t.Fatal(err)
106
+	}
107
+
108
+	b := NewBuilder(runtime)
109
+	container, err := b.Create(config)
110
+	if err != nil {
111
+		t.Fatal(err)
112
+	}
113
+	if err := container.Start(); err != nil {
114
+		t.Fatal(err)
115
+	}
116
+
117
+	if status := container.Wait(); status != 0 {
118
+		t.Fatalf("Expected status code 0, found %d instead", status)
119
+	}
120
+
121
+	img, err := b.Commit(container, "unittester/"+token, "", "", "", nil)
122
+	if err != nil {
123
+		t.Fatal(err)
124
+	}
125
+
126
+	repo := runtime.repositories.Repositories["unittester/"+token]
127
+	err = runtime.graph.PushRepository(ioutil.Discard, "unittester/"+token, repo, runtime.authConfig)
128
+	if err != nil {
129
+		t.Fatal(err)
130
+	}
131
+
132
+	// Remove image so we can pull it again
133
+	if err := runtime.graph.Delete(img.Id); err != nil {
134
+		t.Fatal(err)
135
+	}
136
+
137
+	err = runtime.graph.PullRepository(ioutil.Discard, "unittester/"+token, "", runtime.repositories, runtime.authConfig)
138
+	if err != nil {
139
+		t.Fatal(err)
140
+	}
141
+
142
+	layerPath, err := img.layer()
143
+	if err != nil {
144
+		t.Fatal(err)
145
+	}
146
+
147
+	if _, err := os.Stat(path.Join(layerPath, token)); err != nil {
148
+		t.Fatalf("Error while trying to retrieve token file: %v", err)
149
+	}
150
+}
0 151
deleted file mode 100644
... ...
@@ -1,151 +0,0 @@
1
-package docker
2
-
3
-import (
4
-	"crypto/rand"
5
-	"encoding/hex"
6
-	"github.com/dotcloud/docker/auth"
7
-	"io/ioutil"
8
-	"os"
9
-	"path"
10
-	"testing"
11
-)
12
-
13
-func TestPull(t *testing.T) {
14
-	os.Setenv("DOCKER_INDEX_URL", "")
15
-	runtime, err := newTestRuntime()
16
-	if err != nil {
17
-		t.Fatal(err)
18
-	}
19
-	defer nuke(runtime)
20
-
21
-	err = runtime.graph.PullRepository(ioutil.Discard, "busybox", "", runtime.repositories, nil)
22
-	if err != nil {
23
-		t.Fatal(err)
24
-	}
25
-	img, err := runtime.repositories.LookupImage("busybox")
26
-	if err != nil {
27
-		t.Fatal(err)
28
-	}
29
-
30
-	// Try to run something on this image to make sure the layer's been downloaded properly.
31
-	config, _, err := ParseRun([]string{img.Id, "echo", "Hello World"}, runtime.capabilities)
32
-	if err != nil {
33
-		t.Fatal(err)
34
-	}
35
-
36
-	b := NewBuilder(runtime)
37
-	container, err := b.Create(config)
38
-	if err != nil {
39
-		t.Fatal(err)
40
-	}
41
-	if err := container.Start(); err != nil {
42
-		t.Fatal(err)
43
-	}
44
-
45
-	if status := container.Wait(); status != 0 {
46
-		t.Fatalf("Expected status code 0, found %d instead", status)
47
-	}
48
-}
49
-
50
-func TestPullTag(t *testing.T) {
51
-	os.Setenv("DOCKER_INDEX_URL", "")
52
-	runtime, err := newTestRuntime()
53
-	if err != nil {
54
-		t.Fatal(err)
55
-	}
56
-	defer nuke(runtime)
57
-
58
-	err = runtime.graph.PullRepository(ioutil.Discard, "ubuntu", "12.04", runtime.repositories, nil)
59
-	if err != nil {
60
-		t.Fatal(err)
61
-	}
62
-	_, err = runtime.repositories.LookupImage("ubuntu:12.04")
63
-	if err != nil {
64
-		t.Fatal(err)
65
-	}
66
-
67
-	img2, err := runtime.repositories.LookupImage("ubuntu:12.10")
68
-	if img2 != nil {
69
-		t.Fatalf("Expected nil image but found %v instead", img2.Id)
70
-	}
71
-}
72
-
73
-func login(runtime *Runtime) error {
74
-	authConfig := auth.NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", runtime.root)
75
-	runtime.authConfig = authConfig
76
-	_, err := auth.Login(authConfig)
77
-	return err
78
-}
79
-
80
-func TestPush(t *testing.T) {
81
-	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
82
-	defer os.Setenv("DOCKER_INDEX_URL", "")
83
-	runtime, err := newTestRuntime()
84
-	if err != nil {
85
-		t.Fatal(err)
86
-	}
87
-	defer nuke(runtime)
88
-
89
-	err = login(runtime)
90
-	if err != nil {
91
-		t.Fatal(err)
92
-	}
93
-
94
-	err = runtime.graph.PullRepository(ioutil.Discard, "joffrey/busybox", "", runtime.repositories, nil)
95
-	if err != nil {
96
-		t.Fatal(err)
97
-	}
98
-	tokenBuffer := make([]byte, 16)
99
-	_, err = rand.Read(tokenBuffer)
100
-	if err != nil {
101
-		t.Fatal(err)
102
-	}
103
-	token := hex.EncodeToString(tokenBuffer)[:29]
104
-	config, _, err := ParseRun([]string{"joffrey/busybox", "touch", "/" + token}, runtime.capabilities)
105
-	if err != nil {
106
-		t.Fatal(err)
107
-	}
108
-
109
-	b := NewBuilder(runtime)
110
-	container, err := b.Create(config)
111
-	if err != nil {
112
-		t.Fatal(err)
113
-	}
114
-	if err := container.Start(); err != nil {
115
-		t.Fatal(err)
116
-	}
117
-
118
-	if status := container.Wait(); status != 0 {
119
-		t.Fatalf("Expected status code 0, found %d instead", status)
120
-	}
121
-
122
-	img, err := b.Commit(container, "unittester/"+token, "", "", "", nil)
123
-	if err != nil {
124
-		t.Fatal(err)
125
-	}
126
-
127
-	repo := runtime.repositories.Repositories["unittester/"+token]
128
-	err = runtime.graph.PushRepository(ioutil.Discard, "unittester/"+token, repo, runtime.authConfig)
129
-	if err != nil {
130
-		t.Fatal(err)
131
-	}
132
-
133
-	// Remove image so we can pull it again
134
-	if err := runtime.graph.Delete(img.Id); err != nil {
135
-		t.Fatal(err)
136
-	}
137
-
138
-	err = runtime.graph.PullRepository(ioutil.Discard, "unittester/"+token, "", runtime.repositories, runtime.authConfig)
139
-	if err != nil {
140
-		t.Fatal(err)
141
-	}
142
-
143
-	layerPath, err := img.layer()
144
-	if err != nil {
145
-		t.Fatal(err)
146
-	}
147
-
148
-	if _, err := os.Stat(path.Join(layerPath, token)); err != nil {
149
-		t.Fatalf("Error while trying to retrieve token file: %v", err)
150
-	}
151
-}
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/registry"
5 6
 	"github.com/dotcloud/docker/utils"
6 7
 	"io"
7 8
 	"log"
... ...
@@ -45,7 +46,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
45 45
 }
46 46
 
47 47
 func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
48
-	results, err := srv.runtime.graph.SearchRepositories(nil, term)
48
+	results, err := srv.registry.SearchRepositories(nil, term)
49 49
 	if err != nil {
50 50
 		return nil, err
51 51
 	}
... ...
@@ -284,37 +285,134 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
284 284
 	return nil
285 285
 }
286 286
 
287
-func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
288
-	if registry != "" {
289
-		if err := srv.runtime.graph.PullImage(out, name, registry, nil); err != nil {
290
-			return err
291
-		}
292
-		return nil
293
-	}
294
-	if err := srv.runtime.graph.PullRepository(out, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
287
+func (srv *Server) pullImage(stdout io.Writer, imgId, registry string, token []string) error {
288
+	history, err := srv.registry.GetRemoteHistory(imgId, registry, token)
289
+	if err != nil {
295 290
 		return err
296 291
 	}
292
+
293
+	// FIXME: Try to stream the images?
294
+	// FIXME: Launch the getRemoteImage() in goroutines
295
+	for _, id := range history {
296
+		if !srv.runtime.graph.Exists(id) {
297
+			imgJson, err := srv.registry.GetRemoteImageJson(stdout, id, registry, token)
298
+			if err != nil {
299
+				// FIXME: Keep goging in case of error?
300
+				return err
301
+			}
302
+			img, err := NewImgJson(imgJson)
303
+			if err != nil {
304
+				return fmt.Errorf("Failed to parse json: %s", err)
305
+			}
306
+			if img.Id != imgId {
307
+				return fmt.Errorf("The retrieved image mismatch the requested one. (expected %s, received %s)", imgId, img.Id)
308
+			}
309
+
310
+			// Get the layer
311
+			fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
312
+			layer, err := srv.registry.GetRemoteImageLayer(stdout, img.Id, registry, token)
313
+			if err != nil {
314
+				return err
315
+			}
316
+
317
+			if err := srv.runtime.graph.Register(layer, false, img); err != nil {
318
+				return err
319
+			}
320
+		}
321
+	}
297 322
 	return nil
298 323
 }
299 324
 
300
-func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
301
-	img, err := srv.runtime.graph.Get(name)
325
+func (srv *Server) pullRepository(stdout io.Writer, remote, askedTag string) error {
326
+	utils.Debugf("Retrieving repository data")
327
+	repoData, err := srv.registry.GetRepositoryData(remote)
302 328
 	if err != nil {
303
-		utils.Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
304
-		// If it fails, try to get the repository
305
-		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
306
-			if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil {
329
+		return err
330
+	}
331
+
332
+	utils.Debugf("Updating checksums")
333
+	// Reload the json file to make sure not to overwrite faster sums
334
+	if err := srv.runtime.graph.UpdateChecksuns(repoData.ImgList); err != nil {
335
+		return err
336
+	}
337
+
338
+	utils.Debugf("Retrieving the tag list")
339
+	tagsList, err := srv.registry.GetRemoteTags(stdout, repoData.Endpoints, remote, repoData.Tokens)
340
+	if err != nil {
341
+		return err
342
+	}
343
+	for tag, id := range tagsList {
344
+		repoData.ImgList[id].Tag = tag
345
+	}
346
+
347
+	for _, img := range repoData.ImgList {
348
+		// If we asked for a specific tag, skip all tags expect the wanted one
349
+		if askedTag != "" && askedTag != img.Tag {
350
+			continue
351
+		}
352
+		fmt.Fprintf(stdout, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote)
353
+		success := false
354
+		for _, ep := range repoData.Endpoints {
355
+			if err := srv.pullImage(stdout, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil {
356
+				fmt.Fprintf(stdout, "Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)
357
+				continue
358
+			}
359
+			if err := srv.runtime.repositories.Set(remote, img.Tag, img.Id, true); err != nil {
307 360
 				return err
308 361
 			}
309
-			return nil
362
+			success = true
363
+			delete(tagsList, img.Tag)
364
+			break
310 365
 		}
311
-
366
+		if !success {
367
+			return fmt.Errorf("Could not find repository on any of the indexed registries.")
368
+		}
369
+	}
370
+	for tag, id := range tagsList {
371
+		if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
372
+			return err
373
+		}
374
+	}
375
+	if err := srv.runtime.repositories.Save(); err != nil {
312 376
 		return err
313 377
 	}
314
-	err = srv.runtime.graph.PushImage(out, img, registry, nil)
315
-	if err != nil {
378
+
379
+	return nil
380
+}
381
+
382
+func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
383
+	if registry != "" {
384
+		if err := srv.pullImage(out, name, registry, nil); err != nil {
385
+			return err
386
+		}
387
+		return nil
388
+	}
389
+
390
+	if err := srv.pullRepository(out, name, tag); err != nil {
316 391
 		return err
317 392
 	}
393
+
394
+	return nil
395
+}
396
+
397
+func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
398
+	// img, err := srv.runtime.graph.Get(name)
399
+	// if err != nil {
400
+	// 	utils.Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
401
+	// 	// If it fails, try to get the repository
402
+	// 	if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
403
+	// 		if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil {
404
+	// 			return err
405
+	// 		}
406
+	// 		return nil
407
+	// 	}
408
+
409
+	// 	return err
410
+	// }
411
+	// err = srv.runtime.graph.PushImage(out, img, registry, nil)
412
+	// if err != nil {
413
+	// 	return err
414
+	// }
318 415
 	return nil
319 416
 }
320 417
 
... ...
@@ -565,11 +663,13 @@ func NewServer(autoRestart bool) (*Server, error) {
565 565
 		return nil, err
566 566
 	}
567 567
 	srv := &Server{
568
-		runtime: runtime,
568
+		runtime:  runtime,
569
+		registry: registry.NewRegistry(runtime.authConfig),
569 570
 	}
570 571
 	return srv, nil
571 572
 }
572 573
 
573 574
 type Server struct {
574
-	runtime *Runtime
575
+	runtime  *Runtime
576
+	registry *registry.Registry
575 577
 }