PutBlobWithOptions writes contents of stream and returns data representing the result. inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. inputInfo.Size is the expected length of stream, if known.
(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions)
| 136 | // to any other readers for download using the supplied digest. |
| 137 | // If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlobWithOptions MUST 1) fail, and 2) delete any data stored so far. |
| 138 | func (d *dockerImageDestination) PutBlobWithOptions(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, options private.PutBlobOptions) (private.UploadedBlob, error) { |
| 139 | // If requested, precompute the blob digest to prevent uploading layers that already exist on the registry. |
| 140 | // This functionality is particularly useful when BlobInfoCache has not been populated with compressed digests, |
| 141 | // the source blob is uncompressed, and the destination blob is being compressed "on the fly". |
| 142 | if inputInfo.Digest == "" && d.c.sys != nil && d.c.sys.DockerRegistryPushPrecomputeDigests { |
| 143 | logrus.Debugf("Precomputing digest layer for %s", reference.Path(d.ref.ref)) |
| 144 | streamCopy, cleanup, err := streamdigest.ComputeBlobInfo(d.c.sys, stream, &inputInfo) |
| 145 | if err != nil { |
| 146 | return private.UploadedBlob{}, err |
| 147 | } |
| 148 | defer cleanup() |
| 149 | stream = streamCopy |
| 150 | } |
| 151 | |
| 152 | if inputInfo.Digest != "" { |
| 153 | // This should not really be necessary, at least the copy code calls TryReusingBlob automatically. |
| 154 | // Still, we need to check, if only because the "initiate upload" endpoint does not have a documented "blob already exists" return value. |
| 155 | haveBlob, reusedInfo, err := d.tryReusingExactBlob(ctx, inputInfo, options.Cache) |
| 156 | if err != nil { |
| 157 | return private.UploadedBlob{}, err |
| 158 | } |
| 159 | if haveBlob { |
| 160 | return private.UploadedBlob{Digest: reusedInfo.Digest, Size: reusedInfo.Size}, nil |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | // FIXME? Chunked upload, progress reporting, etc. |
| 165 | uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)) |
| 166 | logrus.Debugf("Uploading %s", uploadPath) |
| 167 | res, err := d.c.makeRequest(ctx, http.MethodPost, uploadPath, nil, nil, v2Auth, nil) |
| 168 | if err != nil { |
| 169 | return private.UploadedBlob{}, err |
| 170 | } |
| 171 | defer res.Body.Close() |
| 172 | if res.StatusCode != http.StatusAccepted { |
| 173 | logrus.Debugf("Error initiating layer upload, response %#v", *res) |
| 174 | return private.UploadedBlob{}, fmt.Errorf("initiating layer upload to %s in %s: %w", uploadPath, d.c.registry, registryHTTPResponseToError(res)) |
| 175 | } |
| 176 | uploadLocation, err := res.Location() |
| 177 | if err != nil { |
| 178 | return private.UploadedBlob{}, fmt.Errorf("determining upload URL: %w", err) |
| 179 | } |
| 180 | |
| 181 | digester, stream := putblobdigest.DigestIfCanonicalUnknown(stream, inputInfo) |
| 182 | sizeCounter := &sizeCounter{} |
| 183 | stream = io.TeeReader(stream, sizeCounter) |
| 184 | |
| 185 | uploadLocation, err = func() (*url.URL, error) { // A scope for defer |
| 186 | uploadReader := uploadreader.NewUploadReader(stream) |
| 187 | // This error text should never be user-visible, we terminate only after makeRequestToResolvedURL |
| 188 | // returns, so there isn’t a way for the error text to be provided to any of our callers. |
| 189 | defer uploadReader.Terminate(errors.New("Reading data from an already terminated upload")) |
| 190 | res, err = d.c.makeRequestToResolvedURL(ctx, http.MethodPatch, uploadLocation, map[string][]string{"Content-Type": {"application/octet-stream"}}, uploadReader, inputInfo.Size, v2Auth, nil) |
| 191 | if err != nil { |
| 192 | logrus.Debugf("Error uploading layer chunked %v", err) |
| 193 | return nil, err |
| 194 | } |
| 195 | defer res.Body.Close() |
no test coverage detected