Browse code

registry.Registry -> registry.Session

renaming this struct to more clearly be session, as that is what it
handles.

Splitting out files for easier readability.

Signed-off-by: Vincent Batts <vbatts@redhat.com>

Vincent Batts authored on 2014/08/07 23:43:06
Showing 8 changed files
... ...
@@ -55,7 +55,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
55 55
 		return job.Error(err)
56 56
 	}
57 57
 
58
-	r, err := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
58
+	r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
59 59
 	if err != nil {
60 60
 		return job.Error(err)
61 61
 	}
... ...
@@ -72,7 +72,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
72 72
 	return engine.StatusOK
73 73
 }
74 74
 
75
-func (s *TagStore) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
75
+func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
76 76
 	out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
77 77
 
78 78
 	repoData, err := r.GetRepositoryData(remoteName)
... ...
@@ -210,7 +210,7 @@ func (s *TagStore) pullRepository(r *registry.Registry, out io.Writer, localName
210 210
 	return nil
211 211
 }
212 212
 
213
-func (s *TagStore) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
213
+func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
214 214
 	history, err := r.GetRemoteHistory(imgID, endpoint, token)
215 215
 	if err != nil {
216 216
 		return err
... ...
@@ -60,7 +60,7 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
60 60
 	return imageList, tagsByImage, nil
61 61
 }
62 62
 
63
-func (s *TagStore) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
63
+func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
64 64
 	out = utils.NewWriteFlusher(out)
65 65
 	utils.Debugf("Local repo: %s", localRepo)
66 66
 	imgList, tagsByImage, err := s.getImageList(localRepo, tag)
... ...
@@ -142,7 +142,7 @@ func (s *TagStore) pushRepository(r *registry.Registry, out io.Writer, localName
142 142
 	return nil
143 143
 }
144 144
 
145
-func (s *TagStore) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
145
+func (s *TagStore) pushImage(r *registry.Session, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
146 146
 	out = utils.NewWriteFlusher(out)
147 147
 	jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
148 148
 	if err != nil {
... ...
@@ -219,7 +219,7 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
219 219
 	}
220 220
 
221 221
 	img, err := s.graph.Get(localName)
222
-	r, err2 := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
222
+	r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
223 223
 	if err2 != nil {
224 224
 		return job.Error(err2)
225 225
 	}
226 226
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package registry
1
+
2
+import (
3
+	"runtime"
4
+
5
+	"github.com/docker/docker/dockerversion"
6
+	"github.com/docker/docker/pkg/parsers/kernel"
7
+	"github.com/docker/docker/utils"
8
+)
9
+
10
+func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
11
+	// FIXME: this replicates the 'info' job.
12
+	httpVersion := make([]utils.VersionInfo, 0, 4)
13
+	httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION})
14
+	httpVersion = append(httpVersion, &simpleVersionInfo{"go", runtime.Version()})
15
+	httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT})
16
+	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
17
+		httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()})
18
+	}
19
+	httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS})
20
+	httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH})
21
+	ud := utils.NewHTTPUserAgentDecorator(httpVersion...)
22
+	md := &utils.HTTPMetaHeadersDecorator{
23
+		Headers: metaHeaders,
24
+	}
25
+	factory := utils.NewHTTPRequestFactory(ud, md)
26
+	return factory
27
+}
28
+
29
+// simpleVersionInfo is a simple implementation of
30
+// the interface VersionInfo, which is used
31
+// to provide version information for some product,
32
+// component, etc. It stores the product name and the version
33
+// in string and returns them on calls to Name() and Version().
34
+type simpleVersionInfo struct {
35
+	name    string
36
+	version string
37
+}
38
+
39
+func (v *simpleVersionInfo) Name() string {
40
+	return v.name
41
+}
42
+
43
+func (v *simpleVersionInfo) Version() string {
44
+	return v.version
45
+}
... ...
@@ -1,33 +1,20 @@
1 1
 package registry
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"crypto/sha256"
6
-	_ "crypto/sha512"
7 4
 	"crypto/tls"
8 5
 	"crypto/x509"
9
-	"encoding/hex"
10 6
 	"encoding/json"
11 7
 	"errors"
12 8
 	"fmt"
13
-	"io"
14 9
 	"io/ioutil"
15 10
 	"net"
16 11
 	"net/http"
17
-	"net/http/cookiejar"
18
-	"net/url"
19 12
 	"os"
20 13
 	"path"
21 14
 	"regexp"
22
-	"runtime"
23
-	"strconv"
24 15
 	"strings"
25 16
 	"time"
26 17
 
27
-	"github.com/docker/docker/dockerversion"
28
-	"github.com/docker/docker/pkg/httputils"
29
-	"github.com/docker/docker/pkg/parsers/kernel"
30
-	"github.com/docker/docker/pkg/tarsum"
31 18
 	"github.com/docker/docker/utils"
32 19
 )
33 20
 
... ...
@@ -297,595 +284,6 @@ func ExpandAndVerifyRegistryUrl(hostname string) (string, error) {
297 297
 	return endpoint, nil
298 298
 }
299 299
 
300
-func setTokenAuth(req *http.Request, token []string) {
301
-	if req.Header.Get("Authorization") == "" { // Don't override
302
-		req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
303
-	}
304
-}
305
-
306
-func (r *Registry) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
307
-	return doRequest(req, r.jar, r.timeout)
308
-}
309
-
310
-// Retrieve the history of a given image from the Registry.
311
-// Return a list of the parent's json (requested image included)
312
-func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
313
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
314
-	if err != nil {
315
-		return nil, err
316
-	}
317
-	setTokenAuth(req, token)
318
-	res, _, err := r.doRequest(req)
319
-	if err != nil {
320
-		return nil, err
321
-	}
322
-	defer res.Body.Close()
323
-	if res.StatusCode != 200 {
324
-		if res.StatusCode == 401 {
325
-			return nil, errLoginRequired
326
-		}
327
-		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
328
-	}
329
-
330
-	jsonString, err := ioutil.ReadAll(res.Body)
331
-	if err != nil {
332
-		return nil, fmt.Errorf("Error while reading the http response: %s", err)
333
-	}
334
-
335
-	utils.Debugf("Ancestry: %s", jsonString)
336
-	history := new([]string)
337
-	if err := json.Unmarshal(jsonString, history); err != nil {
338
-		return nil, err
339
-	}
340
-	return *history, nil
341
-}
342
-
343
-// Check if an image exists in the Registry
344
-// TODO: This method should return the errors instead of masking them and returning false
345
-func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool {
346
-
347
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
348
-	if err != nil {
349
-		utils.Errorf("Error in LookupRemoteImage %s", err)
350
-		return false
351
-	}
352
-	setTokenAuth(req, token)
353
-	res, _, err := r.doRequest(req)
354
-	if err != nil {
355
-		utils.Errorf("Error in LookupRemoteImage %s", err)
356
-		return false
357
-	}
358
-	res.Body.Close()
359
-	return res.StatusCode == 200
360
-}
361
-
362
-// Retrieve an image from the Registry.
363
-func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
364
-	// Get the JSON
365
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
366
-	if err != nil {
367
-		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
368
-	}
369
-	setTokenAuth(req, token)
370
-	res, _, err := r.doRequest(req)
371
-	if err != nil {
372
-		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
373
-	}
374
-	defer res.Body.Close()
375
-	if res.StatusCode != 200 {
376
-		return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
377
-	}
378
-
379
-	// if the size header is not present, then set it to '-1'
380
-	imageSize := -1
381
-	if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
382
-		imageSize, err = strconv.Atoi(hdr)
383
-		if err != nil {
384
-			return nil, -1, err
385
-		}
386
-	}
387
-
388
-	jsonString, err := ioutil.ReadAll(res.Body)
389
-	if err != nil {
390
-		return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
391
-	}
392
-	return jsonString, imageSize, nil
393
-}
394
-
395
-func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
396
-	var (
397
-		retries  = 5
398
-		client   *http.Client
399
-		res      *http.Response
400
-		imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
401
-	)
402
-
403
-	req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
404
-	if err != nil {
405
-		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
406
-	}
407
-	setTokenAuth(req, token)
408
-	for i := 1; i <= retries; i++ {
409
-		res, client, err = r.doRequest(req)
410
-		if err != nil {
411
-			res.Body.Close()
412
-			if i == retries {
413
-				return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
414
-					res.StatusCode, imgID)
415
-			}
416
-			time.Sleep(time.Duration(i) * 5 * time.Second)
417
-			continue
418
-		}
419
-		break
420
-	}
421
-
422
-	if res.StatusCode != 200 {
423
-		res.Body.Close()
424
-		return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
425
-			res.StatusCode, imgID)
426
-	}
427
-
428
-	if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
429
-		utils.Debugf("server supports resume")
430
-		return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
431
-	}
432
-	utils.Debugf("server doesn't support resume")
433
-	return res.Body, nil
434
-}
435
-
436
-func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
437
-	if strings.Count(repository, "/") == 0 {
438
-		// This will be removed once the Registry supports auto-resolution on
439
-		// the "library" namespace
440
-		repository = "library/" + repository
441
-	}
442
-	for _, host := range registries {
443
-		endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
444
-		req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
445
-
446
-		if err != nil {
447
-			return nil, err
448
-		}
449
-		setTokenAuth(req, token)
450
-		res, _, err := r.doRequest(req)
451
-		if err != nil {
452
-			return nil, err
453
-		}
454
-
455
-		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
456
-		defer res.Body.Close()
457
-
458
-		if res.StatusCode != 200 && res.StatusCode != 404 {
459
-			continue
460
-		} else if res.StatusCode == 404 {
461
-			return nil, fmt.Errorf("Repository not found")
462
-		}
463
-
464
-		result := make(map[string]string)
465
-		rawJSON, err := ioutil.ReadAll(res.Body)
466
-		if err != nil {
467
-			return nil, err
468
-		}
469
-		if err := json.Unmarshal(rawJSON, &result); err != nil {
470
-			return nil, err
471
-		}
472
-		return result, nil
473
-	}
474
-	return nil, fmt.Errorf("Could not reach any registry endpoint")
475
-}
476
-
477
-func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
478
-	var endpoints []string
479
-	parsedUrl, err := url.Parse(indexEp)
480
-	if err != nil {
481
-		return nil, err
482
-	}
483
-	var urlScheme = parsedUrl.Scheme
484
-	// The Registry's URL scheme has to match the Index'
485
-	for _, ep := range headers {
486
-		epList := strings.Split(ep, ",")
487
-		for _, epListElement := range epList {
488
-			endpoints = append(
489
-				endpoints,
490
-				fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
491
-		}
492
-	}
493
-	return endpoints, nil
494
-}
495
-
496
-func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
497
-	indexEp := r.indexEndpoint
498
-	repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
499
-
500
-	utils.Debugf("[registry] Calling GET %s", repositoryTarget)
501
-
502
-	req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
503
-	if err != nil {
504
-		return nil, err
505
-	}
506
-	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
507
-		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
508
-	}
509
-	req.Header.Set("X-Docker-Token", "true")
510
-
511
-	res, _, err := r.doRequest(req)
512
-	if err != nil {
513
-		return nil, err
514
-	}
515
-	defer res.Body.Close()
516
-	if res.StatusCode == 401 {
517
-		return nil, errLoginRequired
518
-	}
519
-	// TODO: Right now we're ignoring checksums in the response body.
520
-	// In the future, we need to use them to check image validity.
521
-	if res.StatusCode != 200 {
522
-		return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
523
-	}
524
-
525
-	var tokens []string
526
-	if res.Header.Get("X-Docker-Token") != "" {
527
-		tokens = res.Header["X-Docker-Token"]
528
-	}
529
-
530
-	var endpoints []string
531
-	if res.Header.Get("X-Docker-Endpoints") != "" {
532
-		endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
533
-		if err != nil {
534
-			return nil, err
535
-		}
536
-	} else {
537
-		// Assume the endpoint is on the same host
538
-		u, err := url.Parse(indexEp)
539
-		if err != nil {
540
-			return nil, err
541
-		}
542
-		endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", u.Scheme, req.URL.Host))
543
-	}
544
-
545
-	checksumsJSON, err := ioutil.ReadAll(res.Body)
546
-	if err != nil {
547
-		return nil, err
548
-	}
549
-	remoteChecksums := []*ImgData{}
550
-	if err := json.Unmarshal(checksumsJSON, &remoteChecksums); err != nil {
551
-		return nil, err
552
-	}
553
-
554
-	// Forge a better object from the retrieved data
555
-	imgsData := make(map[string]*ImgData)
556
-	for _, elem := range remoteChecksums {
557
-		imgsData[elem.ID] = elem
558
-	}
559
-
560
-	return &RepositoryData{
561
-		ImgList:   imgsData,
562
-		Endpoints: endpoints,
563
-		Tokens:    tokens,
564
-	}, nil
565
-}
566
-
567
-func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
568
-
569
-	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
570
-
571
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
572
-	if err != nil {
573
-		return err
574
-	}
575
-	setTokenAuth(req, token)
576
-	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
577
-	req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
578
-
579
-	res, _, err := r.doRequest(req)
580
-	if err != nil {
581
-		return fmt.Errorf("Failed to upload metadata: %s", err)
582
-	}
583
-	defer res.Body.Close()
584
-	if len(res.Cookies()) > 0 {
585
-		r.jar.SetCookies(req.URL, res.Cookies())
586
-	}
587
-	if res.StatusCode != 200 {
588
-		errBody, err := ioutil.ReadAll(res.Body)
589
-		if err != nil {
590
-			return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
591
-		}
592
-		var jsonBody map[string]string
593
-		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
594
-			errBody = []byte(err.Error())
595
-		} else if jsonBody["error"] == "Image already exists" {
596
-			return ErrAlreadyExists
597
-		}
598
-		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
599
-	}
600
-	return nil
601
-}
602
-
603
-// Push a local image to the registry
604
-func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
605
-
606
-	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
607
-
608
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
609
-	if err != nil {
610
-		return err
611
-	}
612
-	req.Header.Add("Content-type", "application/json")
613
-	setTokenAuth(req, token)
614
-
615
-	res, _, err := r.doRequest(req)
616
-	if err != nil {
617
-		return fmt.Errorf("Failed to upload metadata: %s", err)
618
-	}
619
-	defer res.Body.Close()
620
-	if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") {
621
-		return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
622
-	}
623
-	if res.StatusCode != 200 {
624
-		errBody, err := ioutil.ReadAll(res.Body)
625
-		if err != nil {
626
-			return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
627
-		}
628
-		var jsonBody map[string]string
629
-		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
630
-			errBody = []byte(err.Error())
631
-		} else if jsonBody["error"] == "Image already exists" {
632
-			return ErrAlreadyExists
633
-		}
634
-		return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody), res)
635
-	}
636
-	return nil
637
-}
638
-
639
-func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
640
-
641
-	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
642
-
643
-	tarsumLayer := &tarsum.TarSum{Reader: layer}
644
-	h := sha256.New()
645
-	h.Write(jsonRaw)
646
-	h.Write([]byte{'\n'})
647
-	checksumLayer := io.TeeReader(tarsumLayer, h)
648
-
649
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
650
-	if err != nil {
651
-		return "", "", err
652
-	}
653
-	req.Header.Add("Content-Type", "application/octet-stream")
654
-	req.ContentLength = -1
655
-	req.TransferEncoding = []string{"chunked"}
656
-	setTokenAuth(req, token)
657
-	res, _, err := r.doRequest(req)
658
-	if err != nil {
659
-		return "", "", fmt.Errorf("Failed to upload layer: %s", err)
660
-	}
661
-	if rc, ok := layer.(io.Closer); ok {
662
-		if err := rc.Close(); err != nil {
663
-			return "", "", err
664
-		}
665
-	}
666
-	defer res.Body.Close()
667
-
668
-	if res.StatusCode != 200 {
669
-		errBody, err := ioutil.ReadAll(res.Body)
670
-		if err != nil {
671
-			return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
672
-		}
673
-		return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res)
674
-	}
675
-
676
-	checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil))
677
-	return tarsumLayer.Sum(jsonRaw), checksumPayload, nil
678
-}
679
-
680
-// push a tag on the registry.
681
-// Remote has the format '<user>/<repo>
682
-func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
683
-	// "jsonify" the string
684
-	revision = "\"" + revision + "\""
685
-	path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
686
-
687
-	req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
688
-	if err != nil {
689
-		return err
690
-	}
691
-	req.Header.Add("Content-type", "application/json")
692
-	setTokenAuth(req, token)
693
-	req.ContentLength = int64(len(revision))
694
-	res, _, err := r.doRequest(req)
695
-	if err != nil {
696
-		return err
697
-	}
698
-	res.Body.Close()
699
-	if res.StatusCode != 200 && res.StatusCode != 201 {
700
-		return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
701
-	}
702
-	return nil
703
-}
704
-
705
-func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
706
-	cleanImgList := []*ImgData{}
707
-	indexEp := r.indexEndpoint
708
-
709
-	if validate {
710
-		for _, elem := range imgList {
711
-			if elem.Checksum != "" {
712
-				cleanImgList = append(cleanImgList, elem)
713
-			}
714
-		}
715
-	} else {
716
-		cleanImgList = imgList
717
-	}
718
-
719
-	imgListJSON, err := json.Marshal(cleanImgList)
720
-	if err != nil {
721
-		return nil, err
722
-	}
723
-	var suffix string
724
-	if validate {
725
-		suffix = "images"
726
-	}
727
-	u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
728
-	utils.Debugf("[registry] PUT %s", u)
729
-	utils.Debugf("Image list pushed to index:\n%s", imgListJSON)
730
-	req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(imgListJSON))
731
-	if err != nil {
732
-		return nil, err
733
-	}
734
-	req.Header.Add("Content-type", "application/json")
735
-	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
736
-	req.ContentLength = int64(len(imgListJSON))
737
-	req.Header.Set("X-Docker-Token", "true")
738
-	if validate {
739
-		req.Header["X-Docker-Endpoints"] = regs
740
-	}
741
-
742
-	res, _, err := r.doRequest(req)
743
-	if err != nil {
744
-		return nil, err
745
-	}
746
-	defer res.Body.Close()
747
-
748
-	// Redirect if necessary
749
-	for res.StatusCode >= 300 && res.StatusCode < 400 {
750
-		utils.Debugf("Redirected to %s", res.Header.Get("Location"))
751
-		req, err = r.reqFactory.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
752
-		if err != nil {
753
-			return nil, err
754
-		}
755
-		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
756
-		req.ContentLength = int64(len(imgListJSON))
757
-		req.Header.Set("X-Docker-Token", "true")
758
-		if validate {
759
-			req.Header["X-Docker-Endpoints"] = regs
760
-		}
761
-		res, _, err := r.doRequest(req)
762
-		if err != nil {
763
-			return nil, err
764
-		}
765
-		defer res.Body.Close()
766
-	}
767
-
768
-	var tokens, endpoints []string
769
-	if !validate {
770
-		if res.StatusCode != 200 && res.StatusCode != 201 {
771
-			errBody, err := ioutil.ReadAll(res.Body)
772
-			if err != nil {
773
-				return nil, err
774
-			}
775
-			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody), res)
776
-		}
777
-		if res.Header.Get("X-Docker-Token") != "" {
778
-			tokens = res.Header["X-Docker-Token"]
779
-			utils.Debugf("Auth token: %v", tokens)
780
-		} else {
781
-			return nil, fmt.Errorf("Index response didn't contain an access token")
782
-		}
783
-
784
-		if res.Header.Get("X-Docker-Endpoints") != "" {
785
-			endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
786
-			if err != nil {
787
-				return nil, err
788
-			}
789
-		} else {
790
-			return nil, fmt.Errorf("Index response didn't contain any endpoints")
791
-		}
792
-	}
793
-	if validate {
794
-		if res.StatusCode != 204 {
795
-			errBody, err := ioutil.ReadAll(res.Body)
796
-			if err != nil {
797
-				return nil, err
798
-			}
799
-			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody), res)
800
-		}
801
-	}
802
-
803
-	return &RepositoryData{
804
-		Tokens:    tokens,
805
-		Endpoints: endpoints,
806
-	}, nil
807
-}
808
-
809
-func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
810
-	utils.Debugf("Index server: %s", r.indexEndpoint)
811
-	u := r.indexEndpoint + "search?q=" + url.QueryEscape(term)
812
-	req, err := r.reqFactory.NewRequest("GET", u, nil)
813
-	if err != nil {
814
-		return nil, err
815
-	}
816
-	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
817
-		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
818
-	}
819
-	req.Header.Set("X-Docker-Token", "true")
820
-	res, _, err := r.doRequest(req)
821
-	if err != nil {
822
-		return nil, err
823
-	}
824
-	defer res.Body.Close()
825
-	if res.StatusCode != 200 {
826
-		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexepected status code %d", res.StatusCode), res)
827
-	}
828
-	rawData, err := ioutil.ReadAll(res.Body)
829
-	if err != nil {
830
-		return nil, err
831
-	}
832
-	result := new(SearchResults)
833
-	err = json.Unmarshal(rawData, result)
834
-	return result, err
835
-}
836
-
837
-func (r *Registry) GetAuthConfig(withPasswd bool) *AuthConfig {
838
-	password := ""
839
-	if withPasswd {
840
-		password = r.authConfig.Password
841
-	}
842
-	return &AuthConfig{
843
-		Username: r.authConfig.Username,
844
-		Password: password,
845
-		Email:    r.authConfig.Email,
846
-	}
847
-}
848
-
849
-type SearchResult struct {
850
-	StarCount   int    `json:"star_count"`
851
-	IsOfficial  bool   `json:"is_official"`
852
-	Name        string `json:"name"`
853
-	IsTrusted   bool   `json:"is_trusted"`
854
-	Description string `json:"description"`
855
-}
856
-
857
-type SearchResults struct {
858
-	Query      string         `json:"query"`
859
-	NumResults int            `json:"num_results"`
860
-	Results    []SearchResult `json:"results"`
861
-}
862
-
863
-type RepositoryData struct {
864
-	ImgList   map[string]*ImgData
865
-	Endpoints []string
866
-	Tokens    []string
867
-}
868
-
869
-type ImgData struct {
870
-	ID              string `json:"id"`
871
-	Checksum        string `json:"checksum,omitempty"`
872
-	ChecksumPayload string `json:"-"`
873
-	Tag             string `json:",omitempty"`
874
-}
875
-
876
-type RegistryInfo struct {
877
-	Version    string `json:"version"`
878
-	Standalone bool   `json:"standalone"`
879
-}
880
-
881
-type Registry struct {
882
-	authConfig    *AuthConfig
883
-	reqFactory    *utils.HTTPRequestFactory
884
-	indexEndpoint string
885
-	jar           *cookiejar.Jar
886
-	timeout       TimeoutType
887
-}
888
-
889 300
 func trustedLocation(req *http.Request) bool {
890 301
 	var (
891 302
 		trusteds = []string{"docker.com", "docker.io"}
... ...
@@ -919,73 +317,3 @@ func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
919 919
 	}
920 920
 	return nil
921 921
 }
922
-
923
-func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string, timeout bool) (r *Registry, err error) {
924
-	r = &Registry{
925
-		authConfig:    authConfig,
926
-		indexEndpoint: indexEndpoint,
927
-	}
928
-
929
-	if timeout {
930
-		r.timeout = ReceiveTimeout
931
-	}
932
-
933
-	r.jar, err = cookiejar.New(nil)
934
-	if err != nil {
935
-		return nil, err
936
-	}
937
-
938
-	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
939
-	// alongside our requests.
940
-	if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") {
941
-		info, err := pingRegistryEndpoint(indexEndpoint)
942
-		if err != nil {
943
-			return nil, err
944
-		}
945
-		if info.Standalone {
946
-			utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint)
947
-			dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password)
948
-			factory.AddDecorator(dec)
949
-		}
950
-	}
951
-
952
-	r.reqFactory = factory
953
-	return r, nil
954
-}
955
-
956
-func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
957
-	// FIXME: this replicates the 'info' job.
958
-	httpVersion := make([]utils.VersionInfo, 0, 4)
959
-	httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION})
960
-	httpVersion = append(httpVersion, &simpleVersionInfo{"go", runtime.Version()})
961
-	httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT})
962
-	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
963
-		httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()})
964
-	}
965
-	httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS})
966
-	httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH})
967
-	ud := utils.NewHTTPUserAgentDecorator(httpVersion...)
968
-	md := &utils.HTTPMetaHeadersDecorator{
969
-		Headers: metaHeaders,
970
-	}
971
-	factory := utils.NewHTTPRequestFactory(ud, md)
972
-	return factory
973
-}
974
-
975
-// simpleVersionInfo is a simple implementation of
976
-// the interface VersionInfo, which is used
977
-// to provide version information for some product,
978
-// component, etc. It stores the product name and the version
979
-// in string and returns them on calls to Name() and Version().
980
-type simpleVersionInfo struct {
981
-	name    string
982
-	version string
983
-}
984
-
985
-func (v *simpleVersionInfo) Name() string {
986
-	return v.name
987
-}
988
-
989
-func (v *simpleVersionInfo) Version() string {
990
-	return v.version
991
-}
... ...
@@ -16,9 +16,9 @@ var (
16 16
 	REPO     = "foo42/bar"
17 17
 )
18 18
 
19
-func spawnTestRegistry(t *testing.T) *Registry {
19
+func spawnTestRegistrySession(t *testing.T) *Session {
20 20
 	authConfig := &AuthConfig{}
21
-	r, err := NewRegistry(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/"), true)
21
+	r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/"), true)
22 22
 	if err != nil {
23 23
 		t.Fatal(err)
24 24
 	}
... ...
@@ -34,7 +34,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
34 34
 }
35 35
 
36 36
 func TestGetRemoteHistory(t *testing.T) {
37
-	r := spawnTestRegistry(t)
37
+	r := spawnTestRegistrySession(t)
38 38
 	hist, err := r.GetRemoteHistory(IMAGE_ID, makeURL("/v1/"), TOKEN)
39 39
 	if err != nil {
40 40
 		t.Fatal(err)
... ...
@@ -46,7 +46,7 @@ func TestGetRemoteHistory(t *testing.T) {
46 46
 }
47 47
 
48 48
 func TestLookupRemoteImage(t *testing.T) {
49
-	r := spawnTestRegistry(t)
49
+	r := spawnTestRegistrySession(t)
50 50
 	found := r.LookupRemoteImage(IMAGE_ID, makeURL("/v1/"), TOKEN)
51 51
 	assertEqual(t, found, true, "Expected remote lookup to succeed")
52 52
 	found = r.LookupRemoteImage("abcdef", makeURL("/v1/"), TOKEN)
... ...
@@ -54,7 +54,7 @@ func TestLookupRemoteImage(t *testing.T) {
54 54
 }
55 55
 
56 56
 func TestGetRemoteImageJSON(t *testing.T) {
57
-	r := spawnTestRegistry(t)
57
+	r := spawnTestRegistrySession(t)
58 58
 	json, size, err := r.GetRemoteImageJSON(IMAGE_ID, makeURL("/v1/"), TOKEN)
59 59
 	if err != nil {
60 60
 		t.Fatal(err)
... ...
@@ -71,7 +71,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
71 71
 }
72 72
 
73 73
 func TestGetRemoteImageLayer(t *testing.T) {
74
-	r := spawnTestRegistry(t)
74
+	r := spawnTestRegistrySession(t)
75 75
 	data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN, 0)
76 76
 	if err != nil {
77 77
 		t.Fatal(err)
... ...
@@ -87,7 +87,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
87 87
 }
88 88
 
89 89
 func TestGetRemoteTags(t *testing.T) {
90
-	r := spawnTestRegistry(t)
90
+	r := spawnTestRegistrySession(t)
91 91
 	tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, TOKEN)
92 92
 	if err != nil {
93 93
 		t.Fatal(err)
... ...
@@ -102,7 +102,7 @@ func TestGetRemoteTags(t *testing.T) {
102 102
 }
103 103
 
104 104
 func TestGetRepositoryData(t *testing.T) {
105
-	r := spawnTestRegistry(t)
105
+	r := spawnTestRegistrySession(t)
106 106
 	parsedUrl, err := url.Parse(makeURL("/v1/"))
107 107
 	if err != nil {
108 108
 		t.Fatal(err)
... ...
@@ -123,7 +123,7 @@ func TestGetRepositoryData(t *testing.T) {
123 123
 }
124 124
 
125 125
 func TestPushImageJSONRegistry(t *testing.T) {
126
-	r := spawnTestRegistry(t)
126
+	r := spawnTestRegistrySession(t)
127 127
 	imgData := &ImgData{
128 128
 		ID:       "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
129 129
 		Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
... ...
@@ -136,7 +136,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
136 136
 }
137 137
 
138 138
 func TestPushImageLayerRegistry(t *testing.T) {
139
-	r := spawnTestRegistry(t)
139
+	r := spawnTestRegistrySession(t)
140 140
 	layer := strings.NewReader("")
141 141
 	_, _, err := r.PushImageLayerRegistry(IMAGE_ID, layer, makeURL("/v1/"), TOKEN, []byte{})
142 142
 	if err != nil {
... ...
@@ -171,7 +171,7 @@ func TestResolveRepositoryName(t *testing.T) {
171 171
 }
172 172
 
173 173
 func TestPushRegistryTag(t *testing.T) {
174
-	r := spawnTestRegistry(t)
174
+	r := spawnTestRegistrySession(t)
175 175
 	err := r.PushRegistryTag("foo42/bar", IMAGE_ID, "stable", makeURL("/v1/"), TOKEN)
176 176
 	if err != nil {
177 177
 		t.Fatal(err)
... ...
@@ -179,7 +179,7 @@ func TestPushRegistryTag(t *testing.T) {
179 179
 }
180 180
 
181 181
 func TestPushImageJSONIndex(t *testing.T) {
182
-	r := spawnTestRegistry(t)
182
+	r := spawnTestRegistrySession(t)
183 183
 	imgData := []*ImgData{
184 184
 		{
185 185
 			ID:       "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
... ...
@@ -207,7 +207,7 @@ func TestPushImageJSONIndex(t *testing.T) {
207 207
 }
208 208
 
209 209
 func TestSearchRepositories(t *testing.T) {
210
-	r := spawnTestRegistry(t)
210
+	r := spawnTestRegistrySession(t)
211 211
 	results, err := r.SearchRepositories("fakequery")
212 212
 	if err != nil {
213 213
 		t.Fatal(err)
... ...
@@ -82,7 +82,7 @@ func (s *Service) Search(job *engine.Job) engine.Status {
82 82
 	job.GetenvJson("authConfig", authConfig)
83 83
 	job.GetenvJson("metaHeaders", metaHeaders)
84 84
 
85
-	r, err := NewRegistry(authConfig, HTTPRequestFactory(metaHeaders), IndexServerAddress(), true)
85
+	r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), IndexServerAddress(), true)
86 86
 	if err != nil {
87 87
 		return job.Error(err)
88 88
 	}
89 89
new file mode 100644
... ...
@@ -0,0 +1,611 @@
0
+package registry
1
+
2
+import (
3
+	"bytes"
4
+	"crypto/sha256"
5
+	_ "crypto/sha512"
6
+	"encoding/hex"
7
+	"encoding/json"
8
+	"fmt"
9
+	"io"
10
+	"io/ioutil"
11
+	"net/http"
12
+	"net/http/cookiejar"
13
+	"net/url"
14
+	"strconv"
15
+	"strings"
16
+	"time"
17
+
18
+	"github.com/docker/docker/pkg/httputils"
19
+	"github.com/docker/docker/pkg/tarsum"
20
+	"github.com/docker/docker/utils"
21
+)
22
+
23
+type Session struct {
24
+	authConfig    *AuthConfig
25
+	reqFactory    *utils.HTTPRequestFactory
26
+	indexEndpoint string
27
+	jar           *cookiejar.Jar
28
+	timeout       TimeoutType
29
+}
30
+
31
+func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string, timeout bool) (r *Session, err error) {
32
+	r = &Session{
33
+		authConfig:    authConfig,
34
+		indexEndpoint: indexEndpoint,
35
+	}
36
+
37
+	if timeout {
38
+		r.timeout = ReceiveTimeout
39
+	}
40
+
41
+	r.jar, err = cookiejar.New(nil)
42
+	if err != nil {
43
+		return nil, err
44
+	}
45
+
46
+	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
47
+	// alongside our requests.
48
+	if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") {
49
+		info, err := pingRegistryEndpoint(indexEndpoint)
50
+		if err != nil {
51
+			return nil, err
52
+		}
53
+		if info.Standalone {
54
+			utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint)
55
+			dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password)
56
+			factory.AddDecorator(dec)
57
+		}
58
+	}
59
+
60
+	r.reqFactory = factory
61
+	return r, nil
62
+}
63
+
64
+func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
65
+	return doRequest(req, r.jar, r.timeout)
66
+}
67
+
68
+// Retrieve the history of a given image from the Registry.
69
+// Return a list of the parent's json (requested image included)
70
+func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
71
+	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	setTokenAuth(req, token)
76
+	res, _, err := r.doRequest(req)
77
+	if err != nil {
78
+		return nil, err
79
+	}
80
+	defer res.Body.Close()
81
+	if res.StatusCode != 200 {
82
+		if res.StatusCode == 401 {
83
+			return nil, errLoginRequired
84
+		}
85
+		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
86
+	}
87
+
88
+	jsonString, err := ioutil.ReadAll(res.Body)
89
+	if err != nil {
90
+		return nil, fmt.Errorf("Error while reading the http response: %s", err)
91
+	}
92
+
93
+	utils.Debugf("Ancestry: %s", jsonString)
94
+	history := new([]string)
95
+	if err := json.Unmarshal(jsonString, history); err != nil {
96
+		return nil, err
97
+	}
98
+	return *history, nil
99
+}
100
+
101
+// Check if an image exists in the Registry
102
+// TODO: This method should return the errors instead of masking them and returning false
103
+func (r *Session) LookupRemoteImage(imgID, registry string, token []string) bool {
104
+
105
+	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
106
+	if err != nil {
107
+		utils.Errorf("Error in LookupRemoteImage %s", err)
108
+		return false
109
+	}
110
+	setTokenAuth(req, token)
111
+	res, _, err := r.doRequest(req)
112
+	if err != nil {
113
+		utils.Errorf("Error in LookupRemoteImage %s", err)
114
+		return false
115
+	}
116
+	res.Body.Close()
117
+	return res.StatusCode == 200
118
+}
119
+
120
+// Retrieve an image from the Registry.
121
+func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
122
+	// Get the JSON
123
+	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
124
+	if err != nil {
125
+		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
126
+	}
127
+	setTokenAuth(req, token)
128
+	res, _, err := r.doRequest(req)
129
+	if err != nil {
130
+		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
131
+	}
132
+	defer res.Body.Close()
133
+	if res.StatusCode != 200 {
134
+		return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
135
+	}
136
+	// if the size header is not present, then set it to '-1'
137
+	imageSize := -1
138
+	if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
139
+		imageSize, err = strconv.Atoi(hdr)
140
+		if err != nil {
141
+			return nil, -1, err
142
+		}
143
+	}
144
+
145
+	jsonString, err := ioutil.ReadAll(res.Body)
146
+	if err != nil {
147
+		return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
148
+	}
149
+	return jsonString, imageSize, nil
150
+}
151
+
152
+func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
153
+	var (
154
+		retries  = 5
155
+		client   *http.Client
156
+		res      *http.Response
157
+		imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
158
+	)
159
+
160
+	req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
161
+	if err != nil {
162
+		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
163
+	}
164
+	setTokenAuth(req, token)
165
+	for i := 1; i <= retries; i++ {
166
+		res, client, err = r.doRequest(req)
167
+		if err != nil {
168
+			res.Body.Close()
169
+			if i == retries {
170
+				return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
171
+					res.StatusCode, imgID)
172
+			}
173
+			time.Sleep(time.Duration(i) * 5 * time.Second)
174
+			continue
175
+		}
176
+		break
177
+	}
178
+
179
+	if res.StatusCode != 200 {
180
+		res.Body.Close()
181
+		return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
182
+			res.StatusCode, imgID)
183
+	}
184
+
185
+	if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
186
+		utils.Debugf("server supports resume")
187
+		return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
188
+	}
189
+	utils.Debugf("server doesn't support resume")
190
+	return res.Body, nil
191
+}
192
+
193
+func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
194
+	if strings.Count(repository, "/") == 0 {
195
+		// This will be removed once the Registry supports auto-resolution on
196
+		// the "library" namespace
197
+		repository = "library/" + repository
198
+	}
199
+	for _, host := range registries {
200
+		endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
201
+		req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
202
+
203
+		if err != nil {
204
+			return nil, err
205
+		}
206
+		setTokenAuth(req, token)
207
+		res, _, err := r.doRequest(req)
208
+		if err != nil {
209
+			return nil, err
210
+		}
211
+
212
+		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
213
+		defer res.Body.Close()
214
+
215
+		if res.StatusCode != 200 && res.StatusCode != 404 {
216
+			continue
217
+		} else if res.StatusCode == 404 {
218
+			return nil, fmt.Errorf("Repository not found")
219
+		}
220
+
221
+		result := make(map[string]string)
222
+		rawJSON, err := ioutil.ReadAll(res.Body)
223
+		if err != nil {
224
+			return nil, err
225
+		}
226
+		if err := json.Unmarshal(rawJSON, &result); err != nil {
227
+			return nil, err
228
+		}
229
+		return result, nil
230
+	}
231
+	return nil, fmt.Errorf("Could not reach any registry endpoint")
232
+}
233
+
234
+func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
235
+	var endpoints []string
236
+	parsedUrl, err := url.Parse(indexEp)
237
+	if err != nil {
238
+		return nil, err
239
+	}
240
+	var urlScheme = parsedUrl.Scheme
241
+	// The Registry's URL scheme has to match the Index'
242
+	for _, ep := range headers {
243
+		epList := strings.Split(ep, ",")
244
+		for _, epListElement := range epList {
245
+			endpoints = append(
246
+				endpoints,
247
+				fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
248
+		}
249
+	}
250
+	return endpoints, nil
251
+}
252
+
253
+func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
254
+	indexEp := r.indexEndpoint
255
+	repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
256
+
257
+	utils.Debugf("[registry] Calling GET %s", repositoryTarget)
258
+
259
+	req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
260
+	if err != nil {
261
+		return nil, err
262
+	}
263
+	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
264
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
265
+	}
266
+	req.Header.Set("X-Docker-Token", "true")
267
+
268
+	res, _, err := r.doRequest(req)
269
+	if err != nil {
270
+		return nil, err
271
+	}
272
+	defer res.Body.Close()
273
+	if res.StatusCode == 401 {
274
+		return nil, errLoginRequired
275
+	}
276
+	// TODO: Right now we're ignoring checksums in the response body.
277
+	// In the future, we need to use them to check image validity.
278
+	if res.StatusCode != 200 {
279
+		return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
280
+	}
281
+
282
+	var tokens []string
283
+	if res.Header.Get("X-Docker-Token") != "" {
284
+		tokens = res.Header["X-Docker-Token"]
285
+	}
286
+
287
+	var endpoints []string
288
+	if res.Header.Get("X-Docker-Endpoints") != "" {
289
+		endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
290
+		if err != nil {
291
+			return nil, err
292
+		}
293
+	} else {
294
+		// Assume the endpoint is on the same host
295
+		u, err := url.Parse(indexEp)
296
+		if err != nil {
297
+			return nil, err
298
+		}
299
+		endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", u.Scheme, req.URL.Host))
300
+	}
301
+
302
+	checksumsJSON, err := ioutil.ReadAll(res.Body)
303
+	if err != nil {
304
+		return nil, err
305
+	}
306
+	remoteChecksums := []*ImgData{}
307
+	if err := json.Unmarshal(checksumsJSON, &remoteChecksums); err != nil {
308
+		return nil, err
309
+	}
310
+
311
+	// Forge a better object from the retrieved data
312
+	imgsData := make(map[string]*ImgData)
313
+	for _, elem := range remoteChecksums {
314
+		imgsData[elem.ID] = elem
315
+	}
316
+
317
+	return &RepositoryData{
318
+		ImgList:   imgsData,
319
+		Endpoints: endpoints,
320
+		Tokens:    tokens,
321
+	}, nil
322
+}
323
+
324
+func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
325
+
326
+	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
327
+
328
+	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
329
+	if err != nil {
330
+		return err
331
+	}
332
+	setTokenAuth(req, token)
333
+	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
334
+	req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
335
+
336
+	res, _, err := r.doRequest(req)
337
+	if err != nil {
338
+		return fmt.Errorf("Failed to upload metadata: %s", err)
339
+	}
340
+	defer res.Body.Close()
341
+	if len(res.Cookies()) > 0 {
342
+		r.jar.SetCookies(req.URL, res.Cookies())
343
+	}
344
+	if res.StatusCode != 200 {
345
+		errBody, err := ioutil.ReadAll(res.Body)
346
+		if err != nil {
347
+			return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
348
+		}
349
+		var jsonBody map[string]string
350
+		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
351
+			errBody = []byte(err.Error())
352
+		} else if jsonBody["error"] == "Image already exists" {
353
+			return ErrAlreadyExists
354
+		}
355
+		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
356
+	}
357
+	return nil
358
+}
359
+
360
+// Push a local image to the registry
361
+func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
362
+
363
+	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
364
+
365
+	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
366
+	if err != nil {
367
+		return err
368
+	}
369
+	req.Header.Add("Content-type", "application/json")
370
+	setTokenAuth(req, token)
371
+
372
+	res, _, err := r.doRequest(req)
373
+	if err != nil {
374
+		return fmt.Errorf("Failed to upload metadata: %s", err)
375
+	}
376
+	defer res.Body.Close()
377
+	if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") {
378
+		return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
379
+	}
380
+	if res.StatusCode != 200 {
381
+		errBody, err := ioutil.ReadAll(res.Body)
382
+		if err != nil {
383
+			return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
384
+		}
385
+		var jsonBody map[string]string
386
+		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
387
+			errBody = []byte(err.Error())
388
+		} else if jsonBody["error"] == "Image already exists" {
389
+			return ErrAlreadyExists
390
+		}
391
+		return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody), res)
392
+	}
393
+	return nil
394
+}
395
+
396
+func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
397
+
398
+	utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
399
+
400
+	tarsumLayer := &tarsum.TarSum{Reader: layer}
401
+	h := sha256.New()
402
+	h.Write(jsonRaw)
403
+	h.Write([]byte{'\n'})
404
+	checksumLayer := io.TeeReader(tarsumLayer, h)
405
+
406
+	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
407
+	if err != nil {
408
+		return "", "", err
409
+	}
410
+	req.Header.Add("Content-Type", "application/octet-stream")
411
+	req.ContentLength = -1
412
+	req.TransferEncoding = []string{"chunked"}
413
+	setTokenAuth(req, token)
414
+	res, _, err := r.doRequest(req)
415
+	if err != nil {
416
+		return "", "", fmt.Errorf("Failed to upload layer: %s", err)
417
+	}
418
+	if rc, ok := layer.(io.Closer); ok {
419
+		if err := rc.Close(); err != nil {
420
+			return "", "", err
421
+		}
422
+	}
423
+	defer res.Body.Close()
424
+
425
+	if res.StatusCode != 200 {
426
+		errBody, err := ioutil.ReadAll(res.Body)
427
+		if err != nil {
428
+			return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
429
+		}
430
+		return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res)
431
+	}
432
+
433
+	checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil))
434
+	return tarsumLayer.Sum(jsonRaw), checksumPayload, nil
435
+}
436
+
437
+// push a tag on the registry.
438
+// Remote has the format '<user>/<repo>
439
+func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
440
+	// "jsonify" the string
441
+	revision = "\"" + revision + "\""
442
+	path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
443
+
444
+	req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
445
+	if err != nil {
446
+		return err
447
+	}
448
+	req.Header.Add("Content-type", "application/json")
449
+	setTokenAuth(req, token)
450
+	req.ContentLength = int64(len(revision))
451
+	res, _, err := r.doRequest(req)
452
+	if err != nil {
453
+		return err
454
+	}
455
+	res.Body.Close()
456
+	if res.StatusCode != 200 && res.StatusCode != 201 {
457
+		return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
458
+	}
459
+	return nil
460
+}
461
+
462
+func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
463
+	cleanImgList := []*ImgData{}
464
+	indexEp := r.indexEndpoint
465
+
466
+	if validate {
467
+		for _, elem := range imgList {
468
+			if elem.Checksum != "" {
469
+				cleanImgList = append(cleanImgList, elem)
470
+			}
471
+		}
472
+	} else {
473
+		cleanImgList = imgList
474
+	}
475
+
476
+	imgListJSON, err := json.Marshal(cleanImgList)
477
+	if err != nil {
478
+		return nil, err
479
+	}
480
+	var suffix string
481
+	if validate {
482
+		suffix = "images"
483
+	}
484
+	u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
485
+	utils.Debugf("[registry] PUT %s", u)
486
+	utils.Debugf("Image list pushed to index:\n%s", imgListJSON)
487
+	req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(imgListJSON))
488
+	if err != nil {
489
+		return nil, err
490
+	}
491
+	req.Header.Add("Content-type", "application/json")
492
+	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
493
+	req.ContentLength = int64(len(imgListJSON))
494
+	req.Header.Set("X-Docker-Token", "true")
495
+	if validate {
496
+		req.Header["X-Docker-Endpoints"] = regs
497
+	}
498
+
499
+	res, _, err := r.doRequest(req)
500
+	if err != nil {
501
+		return nil, err
502
+	}
503
+	defer res.Body.Close()
504
+
505
+	// Redirect if necessary
506
+	for res.StatusCode >= 300 && res.StatusCode < 400 {
507
+		utils.Debugf("Redirected to %s", res.Header.Get("Location"))
508
+		req, err = r.reqFactory.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
509
+		if err != nil {
510
+			return nil, err
511
+		}
512
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
513
+		req.ContentLength = int64(len(imgListJSON))
514
+		req.Header.Set("X-Docker-Token", "true")
515
+		if validate {
516
+			req.Header["X-Docker-Endpoints"] = regs
517
+		}
518
+		res, _, err := r.doRequest(req)
519
+		if err != nil {
520
+			return nil, err
521
+		}
522
+		defer res.Body.Close()
523
+	}
524
+
525
+	var tokens, endpoints []string
526
+	if !validate {
527
+		if res.StatusCode != 200 && res.StatusCode != 201 {
528
+			errBody, err := ioutil.ReadAll(res.Body)
529
+			if err != nil {
530
+				return nil, err
531
+			}
532
+			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody), res)
533
+		}
534
+		if res.Header.Get("X-Docker-Token") != "" {
535
+			tokens = res.Header["X-Docker-Token"]
536
+			utils.Debugf("Auth token: %v", tokens)
537
+		} else {
538
+			return nil, fmt.Errorf("Index response didn't contain an access token")
539
+		}
540
+
541
+		if res.Header.Get("X-Docker-Endpoints") != "" {
542
+			endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
543
+			if err != nil {
544
+				return nil, err
545
+			}
546
+		} else {
547
+			return nil, fmt.Errorf("Index response didn't contain any endpoints")
548
+		}
549
+	}
550
+	if validate {
551
+		if res.StatusCode != 204 {
552
+			errBody, err := ioutil.ReadAll(res.Body)
553
+			if err != nil {
554
+				return nil, err
555
+			}
556
+			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody), res)
557
+		}
558
+	}
559
+
560
+	return &RepositoryData{
561
+		Tokens:    tokens,
562
+		Endpoints: endpoints,
563
+	}, nil
564
+}
565
+
566
+func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
567
+	utils.Debugf("Index server: %s", r.indexEndpoint)
568
+	u := r.indexEndpoint + "search?q=" + url.QueryEscape(term)
569
+	req, err := r.reqFactory.NewRequest("GET", u, nil)
570
+	if err != nil {
571
+		return nil, err
572
+	}
573
+	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
574
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
575
+	}
576
+	req.Header.Set("X-Docker-Token", "true")
577
+	res, _, err := r.doRequest(req)
578
+	if err != nil {
579
+		return nil, err
580
+	}
581
+	defer res.Body.Close()
582
+	if res.StatusCode != 200 {
583
+		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexepected status code %d", res.StatusCode), res)
584
+	}
585
+	rawData, err := ioutil.ReadAll(res.Body)
586
+	if err != nil {
587
+		return nil, err
588
+	}
589
+	result := new(SearchResults)
590
+	err = json.Unmarshal(rawData, result)
591
+	return result, err
592
+}
593
+
594
+func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig {
595
+	password := ""
596
+	if withPasswd {
597
+		password = r.authConfig.Password
598
+	}
599
+	return &AuthConfig{
600
+		Username: r.authConfig.Username,
601
+		Password: password,
602
+		Email:    r.authConfig.Email,
603
+	}
604
+}
605
+
606
+func setTokenAuth(req *http.Request, token []string) {
607
+	if req.Header.Get("Authorization") == "" { // Don't override
608
+		req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
609
+	}
610
+}
0 611
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package registry
1
+
2
+type SearchResult struct {
3
+	StarCount   int    `json:"star_count"`
4
+	IsOfficial  bool   `json:"is_official"`
5
+	Name        string `json:"name"`
6
+	IsTrusted   bool   `json:"is_trusted"`
7
+	Description string `json:"description"`
8
+}
9
+
10
+type SearchResults struct {
11
+	Query      string         `json:"query"`
12
+	NumResults int            `json:"num_results"`
13
+	Results    []SearchResult `json:"results"`
14
+}
15
+
16
+type RepositoryData struct {
17
+	ImgList   map[string]*ImgData
18
+	Endpoints []string
19
+	Tokens    []string
20
+}
21
+
22
+type ImgData struct {
23
+	ID              string `json:"id"`
24
+	Checksum        string `json:"checksum,omitempty"`
25
+	ChecksumPayload string `json:"-"`
26
+	Tag             string `json:",omitempty"`
27
+}
28
+
29
+type RegistryInfo struct {
30
+	Version    string `json:"version"`
31
+	Standalone bool   `json:"standalone"`
32
+}