github.com/kimh/docker@v1.10.0/distribution/pull_v1.go (about)

     1  package distribution
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net"
     9  	"net/url"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/distribution/registry/client/transport"
    15  	"github.com/docker/docker/distribution/metadata"
    16  	"github.com/docker/docker/distribution/xfer"
    17  	"github.com/docker/docker/image"
    18  	"github.com/docker/docker/image/v1"
    19  	"github.com/docker/docker/layer"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/docker/pkg/progress"
    22  	"github.com/docker/docker/pkg/stringid"
    23  	"github.com/docker/docker/reference"
    24  	"github.com/docker/docker/registry"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  type v1Puller struct {
    29  	v1IDService *metadata.V1IDService
    30  	endpoint    registry.APIEndpoint
    31  	config      *ImagePullConfig
    32  	repoInfo    *registry.RepositoryInfo
    33  	session     *registry.Session
    34  }
    35  
    36  func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
    37  	if _, isCanonical := ref.(reference.Canonical); isCanonical {
    38  		// Allowing fallback, because HTTPS v1 is before HTTP v2
    39  		return fallbackError{err: registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
    40  	}
    41  
    42  	tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
    47  	tr := transport.NewTransport(
    48  		// TODO(tiborvass): was ReceiveTimeout
    49  		registry.NewTransport(tlsConfig),
    50  		registry.DockerHeaders(p.config.MetaHeaders)...,
    51  	)
    52  	client := registry.HTTPClient(tr)
    53  	v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
    54  	if err != nil {
    55  		logrus.Debugf("Could not get v1 endpoint: %v", err)
    56  		return fallbackError{err: err}
    57  	}
    58  	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
    59  	if err != nil {
    60  		// TODO(dmcgowan): Check if should fallback
    61  		logrus.Debugf("Fallback from error: %s", err)
    62  		return fallbackError{err: err}
    63  	}
    64  	if err := p.pullRepository(ctx, ref); err != nil {
    65  		// TODO(dmcgowan): Check if should fallback
    66  		return err
    67  	}
    68  	progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry.  Important: This registry version will not be supported in future versions of docker.")
    69  
    70  	return nil
    71  }
    72  
    73  func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
    74  	progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.FullName())
    75  
    76  	repoData, err := p.session.GetRepositoryData(p.repoInfo)
    77  	if err != nil {
    78  		if strings.Contains(err.Error(), "HTTP code: 404") {
    79  			return fmt.Errorf("Error: image %s not found", p.repoInfo.RemoteName())
    80  		}
    81  		// Unexpected HTTP error
    82  		return err
    83  	}
    84  
    85  	logrus.Debugf("Retrieving the tag list")
    86  	var tagsList map[string]string
    87  	tagged, isTagged := ref.(reference.NamedTagged)
    88  	if !isTagged {
    89  		tagsList, err = p.session.GetRemoteTags(repoData.Endpoints, p.repoInfo)
    90  	} else {
    91  		var tagID string
    92  		tagsList = make(map[string]string)
    93  		tagID, err = p.session.GetRemoteTag(repoData.Endpoints, p.repoInfo, tagged.Tag())
    94  		if err == registry.ErrRepoNotFound {
    95  			return fmt.Errorf("Tag %s not found in repository %s", tagged.Tag(), p.repoInfo.FullName())
    96  		}
    97  		tagsList[tagged.Tag()] = tagID
    98  	}
    99  	if err != nil {
   100  		logrus.Errorf("unable to get remote tags: %s", err)
   101  		return err
   102  	}
   103  
   104  	for tag, id := range tagsList {
   105  		repoData.ImgList[id] = &registry.ImgData{
   106  			ID:       id,
   107  			Tag:      tag,
   108  			Checksum: "",
   109  		}
   110  	}
   111  
   112  	layersDownloaded := false
   113  	for _, imgData := range repoData.ImgList {
   114  		if isTagged && imgData.Tag != tagged.Tag() {
   115  			continue
   116  		}
   117  
   118  		err := p.downloadImage(ctx, repoData, imgData, &layersDownloaded)
   119  		if err != nil {
   120  			return err
   121  		}
   122  	}
   123  
   124  	writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded)
   125  	return nil
   126  }
   127  
   128  func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.RepositoryData, img *registry.ImgData, layersDownloaded *bool) error {
   129  	if img.Tag == "" {
   130  		logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
   131  		return nil
   132  	}
   133  
   134  	localNameRef, err := reference.WithTag(p.repoInfo, img.Tag)
   135  	if err != nil {
   136  		retErr := fmt.Errorf("Image (id: %s) has invalid tag: %s", img.ID, img.Tag)
   137  		logrus.Debug(retErr.Error())
   138  		return retErr
   139  	}
   140  
   141  	if err := v1.ValidateID(img.ID); err != nil {
   142  		return err
   143  	}
   144  
   145  	progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, p.repoInfo.FullName())
   146  	success := false
   147  	var lastErr error
   148  	for _, ep := range p.repoInfo.Index.Mirrors {
   149  		ep += "v1/"
   150  		progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, p.repoInfo.FullName(), ep))
   151  		if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil {
   152  			// Don't report errors when pulling from mirrors.
   153  			logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err)
   154  			continue
   155  		}
   156  		success = true
   157  		break
   158  	}
   159  	if !success {
   160  		for _, ep := range repoData.Endpoints {
   161  			progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s, endpoint: %s", img.Tag, p.repoInfo.FullName(), ep)
   162  			if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil {
   163  				// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
   164  				// As the error is also given to the output stream the user will see the error.
   165  				lastErr = err
   166  				progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err)
   167  				continue
   168  			}
   169  			success = true
   170  			break
   171  		}
   172  	}
   173  	if !success {
   174  		err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, p.repoInfo.FullName(), lastErr)
   175  		progress.Update(p.config.ProgressOutput, stringid.TruncateID(img.ID), err.Error())
   176  		return err
   177  	}
   178  	return nil
   179  }
   180  
   181  func (p *v1Puller) pullImage(ctx context.Context, v1ID, endpoint string, localNameRef reference.Named, layersDownloaded *bool) (err error) {
   182  	var history []string
   183  	history, err = p.session.GetRemoteHistory(v1ID, endpoint)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	if len(history) < 1 {
   188  		return fmt.Errorf("empty history for image %s", v1ID)
   189  	}
   190  	progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1ID), "Pulling dependent layers")
   191  
   192  	var (
   193  		descriptors []xfer.DownloadDescriptor
   194  		newHistory  []image.History
   195  		imgJSON     []byte
   196  		imgSize     int64
   197  	)
   198  
   199  	// Iterate over layers, in order from bottom-most to top-most. Download
   200  	// config for all layers and create descriptors.
   201  	for i := len(history) - 1; i >= 0; i-- {
   202  		v1LayerID := history[i]
   203  		imgJSON, imgSize, err = p.downloadLayerConfig(v1LayerID, endpoint)
   204  		if err != nil {
   205  			return err
   206  		}
   207  
   208  		// Create a new-style config from the legacy configs
   209  		h, err := v1.HistoryFromConfig(imgJSON, false)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		newHistory = append(newHistory, h)
   214  
   215  		layerDescriptor := &v1LayerDescriptor{
   216  			v1LayerID:        v1LayerID,
   217  			indexName:        p.repoInfo.Index.Name,
   218  			endpoint:         endpoint,
   219  			v1IDService:      p.v1IDService,
   220  			layersDownloaded: layersDownloaded,
   221  			layerSize:        imgSize,
   222  			session:          p.session,
   223  		}
   224  
   225  		descriptors = append(descriptors, layerDescriptor)
   226  	}
   227  
   228  	rootFS := image.NewRootFS()
   229  	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
   230  	if err != nil {
   231  		return err
   232  	}
   233  	defer release()
   234  
   235  	config, err := v1.MakeConfigFromV1Config(imgJSON, &resultRootFS, newHistory)
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	imageID, err := p.config.ImageStore.Create(config)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	if err := p.config.ReferenceStore.AddTag(localNameRef, imageID, true); err != nil {
   246  		return err
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  func (p *v1Puller) downloadLayerConfig(v1LayerID, endpoint string) (imgJSON []byte, imgSize int64, err error) {
   253  	progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1LayerID), "Pulling metadata")
   254  
   255  	retries := 5
   256  	for j := 1; j <= retries; j++ {
   257  		imgJSON, imgSize, err := p.session.GetRemoteImageJSON(v1LayerID, endpoint)
   258  		if err != nil && j == retries {
   259  			progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1LayerID), "Error pulling layer metadata")
   260  			return nil, 0, err
   261  		} else if err != nil {
   262  			time.Sleep(time.Duration(j) * 500 * time.Millisecond)
   263  			continue
   264  		}
   265  
   266  		return imgJSON, imgSize, nil
   267  	}
   268  
   269  	// not reached
   270  	return nil, 0, nil
   271  }
   272  
   273  type v1LayerDescriptor struct {
   274  	v1LayerID        string
   275  	indexName        string
   276  	endpoint         string
   277  	v1IDService      *metadata.V1IDService
   278  	layersDownloaded *bool
   279  	layerSize        int64
   280  	session          *registry.Session
   281  }
   282  
   283  func (ld *v1LayerDescriptor) Key() string {
   284  	return "v1:" + ld.v1LayerID
   285  }
   286  
   287  func (ld *v1LayerDescriptor) ID() string {
   288  	return stringid.TruncateID(ld.v1LayerID)
   289  }
   290  
   291  func (ld *v1LayerDescriptor) DiffID() (layer.DiffID, error) {
   292  	return ld.v1IDService.Get(ld.v1LayerID, ld.indexName)
   293  }
   294  
   295  func (ld *v1LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) {
   296  	progress.Update(progressOutput, ld.ID(), "Pulling fs layer")
   297  	layerReader, err := ld.session.GetRemoteImageLayer(ld.v1LayerID, ld.endpoint, ld.layerSize)
   298  	if err != nil {
   299  		progress.Update(progressOutput, ld.ID(), "Error pulling dependent layers")
   300  		if uerr, ok := err.(*url.Error); ok {
   301  			err = uerr.Err
   302  		}
   303  		if terr, ok := err.(net.Error); ok && terr.Timeout() {
   304  			return nil, 0, err
   305  		}
   306  		return nil, 0, xfer.DoNotRetry{Err: err}
   307  	}
   308  	*ld.layersDownloaded = true
   309  
   310  	tmpFile, err := ioutil.TempFile("", "GetImageBlob")
   311  	if err != nil {
   312  		layerReader.Close()
   313  		return nil, 0, err
   314  	}
   315  
   316  	reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerReader), progressOutput, ld.layerSize, ld.ID(), "Downloading")
   317  	defer reader.Close()
   318  
   319  	_, err = io.Copy(tmpFile, reader)
   320  	if err != nil {
   321  		return nil, 0, err
   322  	}
   323  
   324  	progress.Update(progressOutput, ld.ID(), "Download complete")
   325  
   326  	logrus.Debugf("Downloaded %s to tempfile %s", ld.ID(), tmpFile.Name())
   327  
   328  	tmpFile.Seek(0, 0)
   329  	return ioutils.NewReadCloserWrapper(tmpFile, tmpFileCloser(tmpFile)), ld.layerSize, nil
   330  }
   331  
   332  func (ld *v1LayerDescriptor) Registered(diffID layer.DiffID) {
   333  	// Cache mapping from this layer's DiffID to the blobsum
   334  	ld.v1IDService.Set(ld.v1LayerID, ld.indexName, diffID)
   335  }