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