func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) { out := p.config.OutStream out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Preparing", nil)) image, err := p.graph.Get(img.ID) if err != nil { return "", err } arch, err := p.graph.TarLayer(image) if err != nil { return "", err } defer arch.Close() // Send the layer layerUpload, err := bs.Create(context.Background()) if err != nil { return "", err } defer layerUpload.Close() digester := digest.Canonical.New() tee := io.TeeReader(arch, digester.Hash()) reader := progressreader.New(progressreader.Config{ In: ioutil.NopCloser(tee), // we'll take care of close here. Out: out, Formatter: p.sf, // TODO(stevvooe): This may cause a size reporting error. Try to get // this from tar-split or elsewhere. The main issue here is that we // don't want to buffer to disk *just* to calculate the size. Size: img.Size, NewLines: false, ID: stringid.TruncateID(img.ID), Action: "Pushing", }) out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushing", nil)) nn, err := io.Copy(layerUpload, reader) if err != nil { return "", err } dgst := digester.Digest() if _, err := layerUpload.Commit(context.Background(), distribution.Descriptor{Digest: dgst}); err != nil { return "", err } logrus.Debugf("uploaded layer %s (%s), %d bytes", img.ID, dgst, nn) out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushed", nil)) return dgst, nil }
func (p *v2Pusher) pushV2Layer(bs distribution.BlobService, l layer.Layer) (digest.Digest, error) { out := p.config.OutStream displayID := stringid.TruncateID(string(l.DiffID())) out.Write(p.sf.FormatProgress(displayID, "Preparing", nil)) arch, err := l.TarStream() if err != nil { return "", err } // Send the layer layerUpload, err := bs.Create(context.Background()) if err != nil { return "", err } defer layerUpload.Close() // don't care if this fails; best effort size, _ := l.DiffSize() reader := progressreader.New(progressreader.Config{ In: ioutil.NopCloser(arch), // we'll take care of close here. Out: out, Formatter: p.sf, Size: size, NewLines: false, ID: displayID, Action: "Pushing", }) compressedReader := compress(reader) digester := digest.Canonical.New() tee := io.TeeReader(compressedReader, digester.Hash()) out.Write(p.sf.FormatProgress(displayID, "Pushing", nil)) nn, err := layerUpload.ReadFrom(tee) compressedReader.Close() if err != nil { return "", err } dgst := digester.Digest() if _, err := layerUpload.Commit(context.Background(), distribution.Descriptor{Digest: dgst}); err != nil { return "", err } logrus.Debugf("uploaded layer %s (%s), %d bytes", l.DiffID(), dgst, nn) out.Write(p.sf.FormatProgress(displayID, "Pushed", nil)) return dgst, nil }
// getOrCreateBlobWriter will track which blobs are currently being downloaded and enable client requesting // the same blob concurrently to read from the existing stream. func getOrCreateBlobWriter(ctx context.Context, blobs distribution.BlobService, desc distribution.Descriptor) (distribution.BlobWriter, bool, cleanupFunc, error) { mu.Lock() defer mu.Unlock() dgst := desc.Digest cleanup := func() { mu.Lock() defer mu.Unlock() inflight[dgst].refCount-- if inflight[dgst].refCount == 0 { defer delete(inflight, dgst) _, err := inflight[dgst].bw.Commit(ctx, desc) if err != nil { // There is a narrow race here where Commit can be called while this blob's TTL is expiring // and its being removed from storage. In that case, the client stream will continue // uninterruped and the blob will be pulled through on the next request, so just log it context.GetLogger(ctx).Errorf("Error committing blob: %q", err) } } } var bw distribution.BlobWriter _, ok := inflight[dgst] if ok { bw = inflight[dgst].bw inflight[dgst].refCount++ return bw, false, cleanup, nil } var err error bw, err = blobs.Create(ctx) if err != nil { return nil, false, nil, err } inflight[dgst] = &inflightBlob{refCount: 1, bw: bw} return bw, true, cleanup, nil }
func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) { out := p.config.OutStream out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Preparing", nil)) image, err := p.graph.Get(img.ID) if err != nil { return "", err } arch, err := p.graph.TarLayer(image) if err != nil { return "", err } defer arch.Close() // Send the layer layerUpload, err := bs.Create(context.Background()) if err != nil { return "", err } defer layerUpload.Close() reader := progressreader.New(progressreader.Config{ In: ioutil.NopCloser(arch), // we'll take care of close here. Out: out, Formatter: p.sf, // TODO(stevvooe): This may cause a size reporting error. Try to get // this from tar-split or elsewhere. The main issue here is that we // don't want to buffer to disk *just* to calculate the size. Size: img.Size, NewLines: false, ID: stringid.TruncateID(img.ID), Action: "Pushing", }) digester := digest.Canonical.New() // HACK: The MultiWriter doesn't write directly to layerUpload because // we must make sure the ReadFrom is used, not Write. Using Write would // send a PATCH request for every Write call. pipeReader, pipeWriter := io.Pipe() // Use a bufio.Writer to avoid excessive chunking in HTTP request. bufWriter := bufio.NewWriterSize(io.MultiWriter(pipeWriter, digester.Hash()), compressionBufSize) compressor := gzip.NewWriter(bufWriter) go func() { _, err := io.Copy(compressor, reader) if err == nil { err = compressor.Close() } if err == nil { err = bufWriter.Flush() } if err != nil { pipeWriter.CloseWithError(err) } else { pipeWriter.Close() } }() out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushing", nil)) nn, err := layerUpload.ReadFrom(pipeReader) pipeReader.Close() if err != nil { return "", err } dgst := digester.Digest() if _, err := layerUpload.Commit(context.Background(), distribution.Descriptor{Digest: dgst}); err != nil { return "", err } logrus.Debugf("uploaded layer %s (%s), %d bytes", img.ID, dgst, nn) out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushed", nil)) return dgst, nil }
func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) { out := p.config.OutStream out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) image, err := p.graph.Get(img.ID) if err != nil { return "", err } arch, err := p.graph.TarLayer(image) if err != nil { return "", err } tf, err := p.graph.newTempFile() if err != nil { return "", err } defer func() { tf.Close() os.Remove(tf.Name()) }() size, dgst, err := bufferToFile(tf, arch) if err != nil { return "", err } // Send the layer logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) layerUpload, err := bs.Create(nil) if err != nil { return "", err } defer layerUpload.Close() reader := progressreader.New(progressreader.Config{ In: ioutil.NopCloser(tf), Out: out, Formatter: p.sf, Size: int(size), NewLines: false, ID: stringid.TruncateID(img.ID), Action: "Pushing", }) n, err := layerUpload.ReadFrom(reader) if err != nil { return "", err } if n != size { return "", fmt.Errorf("short upload: only wrote %d of %d", n, size) } desc := distribution.Descriptor{Digest: dgst} if _, err := layerUpload.Commit(nil, desc); err != nil { return "", err } out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil)) return dgst, nil }