Codebase list golang-github-containers-image / ca5fe04
Add manifest list support Add the manifest.List interface, and implementations for OCIv1 Index and Docker Schema2List documents. Add an instanceDigest parameter to PutManifest(), PutSignatures(), and LayerInfosForCopy, for symmetry with GetManifest() and GetSignatures(). Return an error if the instanceDigest is supplied to destinations which don't support them, and add stubs that do so even to the transports which would support it, so that we don't break compilation here. Add a MultipleImages flag to copy.Options, and if the source for a copy operation contains multiple images, copy all of the images if we can. If we can't copy them all, but we were told to, return an error. Use the generic manifest list API to select a single image to copy from a list, so that we aren't just limited to the Docker manifest list format for those cases. When guessing at the type of a manifest, if the manifest contains a list of manifests, use its declared MIME type if it included one, else assume it's an OCI index, because an OCI index doesn't include its MIME type. When copying, switch from using an encode-then-compare of the original and updated versions of the list to checking if the instance list was changed (one of the things we might have changed) or if its type has changed due to conversion (the other change we might have made). If neither has changed, then we don't need to change the encoded value of the manifest. When copying, when checking for a digest mismatch in a target image reference, ignore a mismatch between the digest in the reference and the digest of the main manifest if we're copying one element from a list, and the digest in the reference matches the digest of the manifest list. When copying, if conversion of manifests for single images is being forced, convert manifest lists to the corresponding list types. When copying, supply the unparsed top level to Commit() by attaching the value to the context.Context. Support manifest lists in the directory transport by using the instance digest as a prefix of the filename used to store a manifest or a piece of signature data. Support manifest lists in the oci-layout transport by accepting indexes as we do images, and stop guessing about Platform values to add to the top-level index. Support storing manifest lists to registries in the docker: transport by using the manifest digest when we're writing one image as part of pushing a list of them, and by using the instance digest when reading or writing signature data, when one is specified, or the cached digest of the non-instanced digest when one is not specified. Add partial support for manifest lists to the storage transport: when committing one image from a list into storage, also add a copy of the manifest list by extracting it from the context.Context. The logic is already in place to enable locating an image using any of multiple manifest digests. When writing an image that has an instanceDigest value (meaning it's a secondary image), don't try to generate a canonical reference to add to the image's list of names if the reference for the primary image doesn't contain a name. That should only happen if we're writing using just an image ID, which is unlikely, but we still need to handle it. Avoid computing the digest of the manifest, or retrieving the either-a-tag-or-a-digest value from the target reference, if we're given an instanceDigest, which would override them anyway. Move the check for non-nil instanceDigest values up into the main PutSignatures() method instead of duplicating it in the per-strategy helpers. Add mention of the instanceDigest parameter and its use to various PutManifest, PutSignatures, and LayerInfosForCopy implementations and their declarations in interfaces. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com> Nalin Dahyabhai 4 years ago
44 changed file(s) with 1944 addition(s) and 529 deletion(s). Raw diff Collapse all Expand all
2121 "github.com/containers/image/v4/transports"
2222 "github.com/containers/image/v4/types"
2323 digest "github.com/opencontainers/go-digest"
24 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
2425 "github.com/pkg/errors"
2526 "github.com/sirupsen/logrus"
2627 "github.com/vbauerster/mpb"
109110 canSubstituteBlobs bool
110111 }
111112
113 const (
114 // CopySystemImage is the default value which, when set in
115 // Options.ImageListSelection, indicates that the caller expects only one
116 // image to be copied, so if the source reference refers to a list of
117 // images, one that matches the current system will be selected.
118 CopySystemImage ImageListSelection = iota
119 // CopyAllImages is a value which, when set in Options.ImageListSelection,
120 // indicates that the caller expects to copy multiple images, and if
121 // the source reference refers to a list, that the list and every image
122 // to which it refers will be copied. If the source reference refers
123 // to a list, the target reference can not accept lists, an error
124 // should be returned.
125 CopyAllImages
126 // CopySpecificImages is a value which, when set in
127 // Options.ImageListSelection, indicates that the caller expects the
128 // source reference to be either a single image or a list of images,
129 // and if the source reference is a list, wants only specific instances
130 // from it copied (or none of them, if the list of instances to copy is
131 // empty), along with the list itself. If the target reference can
132 // only accept one image (i.e., it cannot accept lists), an error
133 // should be returned.
134 CopySpecificImages
135 )
136
137 // ImageListSelection is one of CopySystemImage, CopyAllImages, or
138 // CopySpecificImages, to control whether, when the source reference is a list,
139 // copy.Image() copies only an image which matches the current runtime
140 // environment, or all images which match the supplied reference, or only
141 // specific images from the source reference.
142 type ImageListSelection int
143
112144 // Options allows supplying non-default configuration modifying the behavior of CopyImage.
113145 type Options struct {
114146 RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature.
120152 Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset.
121153 // manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type
122154 ForceManifestMIMEType string
155 ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list
156 Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself
157 }
158
159 // validateImageListSelection returns an error if the passed-in value is not one that we recognize as a valid ImageListSelection value
160 func validateImageListSelection(selection ImageListSelection) error {
161 switch selection {
162 case CopySystemImage, CopyAllImages, CopySpecificImages:
163 return nil
164 default:
165 return errors.Errorf("Invalid value for options.ImageListSelection: %d", selection)
166 }
123167 }
124168
125169 // Image copies image from srcRef to destRef, using policyContext to validate
126170 // source image admissibility. It returns the manifest which was written to
127171 // the new copy of the image.
128 func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (manifest []byte, retErr error) {
172 func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (copiedManifest []byte, retErr error) {
129173 // NOTE this function uses an output parameter for the error return value.
130174 // Setting this and returning is the ideal way to return an error.
131175 //
133177 // which can be valuable context in the middle of a multi-streamed copy.
134178 if options == nil {
135179 options = &Options{}
180 }
181
182 if err := validateImageListSelection(options.ImageListSelection); err != nil {
183 return nil, err
136184 }
137185
138186 reportWriter := ioutil.Discard
205253 }
206254
207255 if !multiImage {
208 // The simple case: Just copy a single image.
209 if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel); err != nil {
256 // The simple case: just copy a single image.
257 if copiedManifest, _, _, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedToplevel, nil); err != nil {
210258 return nil, err
211259 }
260 } else if options.ImageListSelection == CopySystemImage {
261 // This is a manifest list, and we weren't asked to copy multiple images. Choose a single image that
262 // matches the current system to copy, and copy it.
263 mfest, manifestType, err := unparsedToplevel.Manifest(ctx)
264 if err != nil {
265 return nil, errors.Wrapf(err, "Error reading manifest for %s", transports.ImageName(srcRef))
266 }
267 manifestList, err := manifest.ListFromBlob(mfest, manifestType)
268 if err != nil {
269 return nil, errors.Wrapf(err, "Error parsing primary manifest as list for %s", transports.ImageName(srcRef))
270 }
271 instanceDigest, err := manifestList.ChooseInstance(options.DestinationCtx) // try to pick one that matches options.DestinationCtx
272 if err != nil {
273 return nil, errors.Wrapf(err, "Error choosing an image from manifest list %s", transports.ImageName(srcRef))
274 }
275 logrus.Debugf("Source is a manifest list; copying (only) instance %s for current system", instanceDigest)
276 unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest)
277
278 if copiedManifest, _, _, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, nil); err != nil {
279 return nil, err
280 }
281 } else { /* options.ImageListSelection == CopyAllImages or options.ImageListSelection == CopySpecificImages, */
282 // If we were asked to copy multiple images and can't, that's an error.
283 if !supportsMultipleImages(c.dest) {
284 return nil, errors.Errorf("Error copying multiple images: destination transport %q does not support copying multiple images as a group", destRef.Transport().Name())
285 }
286 // Copy some or all of the images.
287 switch options.ImageListSelection {
288 case CopyAllImages:
289 logrus.Debugf("Source is a manifest list; copying all instances")
290 case CopySpecificImages:
291 logrus.Debugf("Source is a manifest list; copying some instances")
292 }
293 if copiedManifest, _, err = c.copyMultipleImages(ctx, policyContext, options, unparsedToplevel); err != nil {
294 return nil, err
295 }
296 }
297
298 if err := c.dest.Commit(ctx, unparsedToplevel); err != nil {
299 return nil, errors.Wrap(err, "Error committing the finished image")
300 }
301
302 return copiedManifest, nil
303 }
304
305 // Checks if the destination supports accepting multiple images by checking if it can support
306 // manifest types that are lists of other manifests.
307 func supportsMultipleImages(dest types.ImageDestination) bool {
308 mtypes := dest.SupportedManifestMIMETypes()
309 if len(mtypes) == 0 {
310 // Anything goes!
311 return true
312 }
313 for _, mtype := range mtypes {
314 if manifest.MIMETypeIsMultiImage(mtype) {
315 return true
316 }
317 }
318 return false
319 }
320
321 // copyMultipleImages copies some or all of an image list's instances, using
322 // policyContext to validate source image admissibility.
323 func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel *image.UnparsedImage) (copiedManifest []byte, copiedManifestType string, retErr error) {
324 // Parse the list and get a copy of the original value after it's re-encoded.
325 manifestList, manifestType, err := unparsedToplevel.Manifest(ctx)
326 if err != nil {
327 return nil, "", errors.Wrapf(err, "Error reading manifest list")
328 }
329 list, err := manifest.ListFromBlob(manifestList, manifestType)
330 if err != nil {
331 return nil, "", errors.Wrapf(err, "Error parsing manifest list %q", string(manifestList))
332 }
333 originalList := list.Clone()
334
335 // Read and/or clear the set of signatures for this list.
336 var sigs [][]byte
337 if options.RemoveSignatures {
338 sigs = [][]byte{}
212339 } else {
213 // This is a manifest list. Choose a single image and copy it.
214 // FIXME: Copy to destinations which support manifest lists, one image at a time.
215 instanceDigest, err := image.ChooseManifestInstanceFromManifestList(ctx, options.SourceCtx, unparsedToplevel)
216 if err != nil {
217 return nil, errors.Wrapf(err, "Error choosing an image from manifest list %s", transports.ImageName(srcRef))
218 }
219 logrus.Debugf("Source is a manifest list; copying (only) instance %s", instanceDigest)
220 unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest)
221
222 if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedInstance); err != nil {
223 return nil, err
224 }
225 }
226
227 if err := c.dest.Commit(ctx); err != nil {
228 return nil, errors.Wrap(err, "Error committing the finished image")
229 }
230
231 return manifest, nil
232 }
233
234 // Image copies a single (on-manifest-list) image unparsedImage, using policyContext to validate
340 c.Printf("Getting image list signatures\n")
341 s, err := c.rawSource.GetSignatures(ctx, nil)
342 if err != nil {
343 return nil, "", errors.Wrap(err, "Error reading signatures")
344 }
345 sigs = s
346 }
347 if len(sigs) != 0 {
348 c.Printf("Checking if image list destination supports signatures\n")
349 if err := c.dest.SupportsSignatures(ctx); err != nil {
350 return nil, "", errors.Wrap(err, "Can not copy signatures")
351 }
352 }
353
354 // Determine if we'll need to convert the manifest list to a different format.
355 forceListMIMEType := options.ForceManifestMIMEType
356 switch forceListMIMEType {
357 case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema2MediaType:
358 forceListMIMEType = manifest.DockerV2ListMediaType
359 case imgspecv1.MediaTypeImageManifest:
360 forceListMIMEType = imgspecv1.MediaTypeImageIndex
361 }
362 selectedListType, err := c.determineListConversion(manifestType, c.dest.SupportedManifestMIMETypes(), forceListMIMEType)
363 if err != nil {
364 return nil, "", errors.Wrapf(err, "Error determining manifest list type to write to destination")
365 }
366 if selectedListType != list.MIMEType() {
367 canModifyManifestList := (len(sigs) == 0)
368 if !canModifyManifestList {
369 return nil, "", errors.Errorf("Error: manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", selectedListType)
370 }
371 }
372
373 // Copy each image, or just the ones we want to copy, in turn.
374 instanceDigests := list.Instances()
375 imagesToCopy := len(instanceDigests)
376 if options.ImageListSelection == CopySpecificImages {
377 imagesToCopy = len(options.Instances)
378 }
379 c.Printf("Copying %d of %d images in list\n", imagesToCopy, len(instanceDigests))
380 updates := make([]manifest.ListUpdate, len(instanceDigests))
381 instancesCopied := 0
382 for i, instanceDigest := range instanceDigests {
383 if options.ImageListSelection == CopySpecificImages {
384 skip := true
385 for _, instance := range options.Instances {
386 if instance == instanceDigest {
387 skip = false
388 break
389 }
390 }
391 if skip {
392 update, err := list.Instance(instanceDigest)
393 if err != nil {
394 return nil, "", err
395 }
396 logrus.Debugf("Skipping instance %s (%d/%d)", instanceDigest, i+1, len(instanceDigests))
397 // Record the digest/size/type of the manifest that we didn't copy.
398 updates[i] = update
399 continue
400 }
401 }
402 logrus.Debugf("Copying instance %s (%d/%d)", instanceDigest, i+1, len(instanceDigests))
403 c.Printf("Copying image %s (%d/%d)\n", instanceDigest, instancesCopied+1, imagesToCopy)
404 unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceDigest)
405 updatedManifest, updatedManifestType, updatedManifestDigest, err := c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, &instanceDigest)
406 if err != nil {
407 return nil, "", err
408 }
409 instancesCopied++
410 // Record the result of a possible conversion here.
411 update := manifest.ListUpdate{
412 Digest: updatedManifestDigest,
413 Size: int64(len(updatedManifest)),
414 MediaType: updatedManifestType,
415 }
416 updates[i] = update
417 }
418
419 // Now reset the digest/size/types of the manifests in the list to account for any conversions that we made.
420 if err = list.UpdateInstances(updates); err != nil {
421 return nil, "", errors.Wrapf(err, "Error updating manifest list")
422 }
423
424 // Check if the updates meaningfully changed the list of images.
425 listIsModified := false
426 if !reflect.DeepEqual(list.Instances(), originalList.Instances()) {
427 listIsModified = true
428 }
429
430 // Perform the list conversion.
431 if selectedListType != list.MIMEType() {
432 list, err = list.ConvertToMIMEType(selectedListType)
433 if err != nil {
434 return nil, "", errors.Wrapf(err, "Error converting manifest list to list with MIME type %q", selectedListType)
435 }
436 }
437
438 // If we can't use the original value, but we have to change it, flag an error.
439 if listIsModified {
440 manifestList, err = list.Serialize()
441 if err != nil {
442 return nil, "", errors.Wrapf(err, "Error encoding updated manifest list (%q: %#v)", list.MIMEType(), list.Instances())
443 }
444 logrus.Debugf("Manifest list has been updated")
445 }
446
447 // Save the manifest list.
448 c.Printf("Writing manifest list to image destination\n")
449 if err = c.dest.PutManifest(ctx, manifestList, nil); err != nil {
450 return nil, "", errors.Wrapf(err, "Error writing manifest list %q", string(manifestList))
451 }
452
453 // Sign the manifest list.
454 if options.SignBy != "" {
455 newSig, err := c.createSignature(manifestList, options.SignBy)
456 if err != nil {
457 return nil, "", err
458 }
459 sigs = append(sigs, newSig)
460 }
461
462 c.Printf("Storing list signatures\n")
463 if err := c.dest.PutSignatures(ctx, sigs, nil); err != nil {
464 return nil, "", errors.Wrap(err, "Error writing signatures")
465 }
466
467 return manifestList, selectedListType, nil
468 }
469
470 // copyOneImage copies a single (non-manifest-list) image unparsedImage, using policyContext to validate
235471 // source image admissibility.
236 func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifestBytes []byte, retErr error) {
472 func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel, unparsedImage *image.UnparsedImage, targetInstance *digest.Digest) (retManifest []byte, retManifestType string, retManifestDigest digest.Digest, retErr error) {
237473 // The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list.
238474 // Make sure we fail cleanly in such cases.
239475 multiImage, err := isMultiImage(ctx, unparsedImage)
240476 if err != nil {
241477 // FIXME FIXME: How to name a reference for the sub-image?
242 return nil, errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference()))
478 return nil, "", "", errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference()))
243479 }
244480 if multiImage {
245 return nil, fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
481 return nil, "", "", fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
246482 }
247483
248484 // Please keep this policy check BEFORE reading any other information about the image.
249 // (the multiImage check above only matches the MIME type, which we have received anyway.
485 // (The multiImage check above only matches the MIME type, which we have received anyway.
250486 // Actual parsing of anything should be deferred.)
251487 if allowed, err := policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
252 return nil, errors.Wrap(err, "Source image rejected")
488 return nil, "", "", errors.Wrap(err, "Source image rejected")
253489 }
254490 src, err := image.FromUnparsedImage(ctx, options.SourceCtx, unparsedImage)
255491 if err != nil {
256 return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
492 return nil, "", "", errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
257493 }
258494
259495 // If the destination is a digested reference, make a note of that, determine what digest value we're
260 // expecting, and check that the source manifest matches it.
496 // expecting, and check that the source manifest matches it. If the source manifest doesn't, but it's
497 // one item from a manifest list that matches it, accept that as a match.
261498 destIsDigestedReference := false
262499 if named := c.dest.Reference().DockerReference(); named != nil {
263500 if digested, ok := named.(reference.Digested); ok {
264501 destIsDigestedReference = true
265502 sourceManifest, _, err := src.Manifest(ctx)
266503 if err != nil {
267 return nil, errors.Wrapf(err, "Error reading manifest from source image")
504 return nil, "", "", errors.Wrapf(err, "Error reading manifest from source image")
268505 }
269506 matches, err := manifest.MatchesDigest(sourceManifest, digested.Digest())
270507 if err != nil {
271 return nil, errors.Wrapf(err, "Error computing digest of source image's manifest")
508 return nil, "", "", errors.Wrapf(err, "Error computing digest of source image's manifest")
272509 }
273510 if !matches {
274 return nil, errors.New("Digest of source image's manifest would not match destination reference")
511 manifestList, _, err := unparsedToplevel.Manifest(ctx)
512 if err != nil {
513 return nil, "", "", errors.Wrapf(err, "Error reading manifest from source image")
514 }
515 matches, err = manifest.MatchesDigest(manifestList, digested.Digest())
516 if err != nil {
517 return nil, "", "", errors.Wrapf(err, "Error computing digest of source image's manifest")
518 }
519 if !matches {
520 return nil, "", "", errors.New("Digest of source image's manifest would not match destination reference")
521 }
275522 }
276523 }
277524 }
278525
279526 if err := checkImageDestinationForCurrentRuntimeOS(ctx, options.DestinationCtx, src, c.dest); err != nil {
280 return nil, err
527 return nil, "", "", err
281528 }
282529
283530 var sigs [][]byte
287534 c.Printf("Getting image source signatures\n")
288535 s, err := src.Signatures(ctx)
289536 if err != nil {
290 return nil, errors.Wrap(err, "Error reading signatures")
537 return nil, "", "", errors.Wrap(err, "Error reading signatures")
291538 }
292539 sigs = s
293540 }
294541 if len(sigs) != 0 {
295542 c.Printf("Checking if image destination supports signatures\n")
296543 if err := c.dest.SupportsSignatures(ctx); err != nil {
297 return nil, errors.Wrap(err, "Can not copy signatures")
544 return nil, "", "", errors.Wrap(err, "Can not copy signatures")
298545 }
299546 }
300547
314561 ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == ""
315562
316563 if err := ic.updateEmbeddedDockerReference(); err != nil {
317 return nil, err
564 return nil, "", "", err
318565 }
319566
320567 // We compute preferredManifestMIMEType only to show it in error messages.
321568 // Without having to add this context in an error message, we would be happy enough to know only that no conversion is needed.
322569 preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := ic.determineManifestConversion(ctx, c.dest.SupportedManifestMIMETypes(), options.ForceManifestMIMEType)
323570 if err != nil {
324 return nil, err
571 return nil, "", "", err
325572 }
326573
327574 // If src.UpdatedImageNeedsLayerDiffIDs(ic.manifestUpdates) will be true, it needs to be true by the time we get here.
328575 ic.diffIDsAreNeeded = src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates)
329576
330577 if err := ic.copyLayers(ctx); err != nil {
331 return nil, err
578 return nil, "", "", err
332579 }
333580
334581 // With docker/distribution registries we do not know whether the registry accepts schema2 or schema1 only;
335582 // and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support
336583 // without actually trying to upload something and getting a types.ManifestTypeRejectedError.
337584 // So, try the preferred manifest MIME type. If the process succeeds, fine…
338 manifestBytes, err = ic.copyUpdatedConfigAndManifest(ctx)
585 manifestBytes, retManifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance)
586 retManifestType = preferredManifestMIMEType
339587 if err != nil {
340588 logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err)
341589 // … if it fails, _and_ the failure is because the manifest is rejected, we may have other options.
343591 // We don’t have other options.
344592 // In principle the code below would handle this as well, but the resulting error message is fairly ugly.
345593 // Don’t bother the user with MIME types if we have no choice.
346 return nil, err
594 return nil, "", "", err
347595 }
348596 // If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType.
349597 // So if we are here, we will definitely be trying to convert the manifest.
350598 // With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason,
351599 // so let’s bail out early and with a better error message.
352600 if !ic.canModifyManifest {
353 return nil, errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
601 return nil, "", "", errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
354602 }
355603
356604 // errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
358606 for _, manifestMIMEType := range otherManifestMIMETypeCandidates {
359607 logrus.Debugf("Trying to use manifest type %s…", manifestMIMEType)
360608 ic.manifestUpdates.ManifestMIMEType = manifestMIMEType
361 attemptedManifest, err := ic.copyUpdatedConfigAndManifest(ctx)
609 attemptedManifest, attemptedManifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance)
362610 if err != nil {
363611 logrus.Debugf("Upload of manifest type %s failed: %v", manifestMIMEType, err)
364612 errs = append(errs, fmt.Sprintf("%s(%v)", manifestMIMEType, err))
367615
368616 // We have successfully uploaded a manifest.
369617 manifestBytes = attemptedManifest
618 retManifestDigest = attemptedManifestDigest
619 retManifestType = manifestMIMEType
370620 errs = nil // Mark this as a success so that we don't abort below.
371621 break
372622 }
373623 if errs != nil {
374 return nil, fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
624 return nil, "", "", fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
375625 }
376626 }
377627
378628 if options.SignBy != "" {
379629 newSig, err := c.createSignature(manifestBytes, options.SignBy)
380630 if err != nil {
381 return nil, err
631 return nil, "", "", err
382632 }
383633 sigs = append(sigs, newSig)
384634 }
385635
386636 c.Printf("Storing signatures\n")
387 if err := c.dest.PutSignatures(ctx, sigs); err != nil {
388 return nil, errors.Wrap(err, "Error writing signatures")
389 }
390
391 return manifestBytes, nil
637 if err := c.dest.PutSignatures(ctx, sigs, targetInstance); err != nil {
638 return nil, "", "", errors.Wrap(err, "Error writing signatures")
639 }
640
641 return manifestBytes, retManifestType, retManifestDigest, nil
392642 }
393643
394644 // Printf writes a formatted string to c.reportWriter.
553803 }
554804
555805 // copyUpdatedConfigAndManifest updates the image per ic.manifestUpdates, if necessary,
556 // stores the resulting config and manifest to the destination, and returns the stored manifest.
557 func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context) ([]byte, error) {
806 // stores the resulting config and manifest to the destination, and returns the stored manifest
807 // and its digest.
808 func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, digest.Digest, error) {
558809 pendingImage := ic.src
559810 if !reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly}) {
560811 if !ic.canModifyManifest {
561 return nil, errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden")
812 return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden")
562813 }
563814 if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) {
564815 // We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion.
567818 // when ic.c.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2.
568819 // Supposedly s2-only registries do not exist or are extremely rare, so failing with this error message is good enough for now.
569820 // If handling such registries turns out to be necessary, we could compute ic.diffIDsAreNeeded based on the full list of manifest MIME type candidates.
570 return nil, errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType)
821 return nil, "", errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType)
571822 }
572823 pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates)
573824 if err != nil {
574 return nil, errors.Wrap(err, "Error creating an updated image manifest")
825 return nil, "", errors.Wrap(err, "Error creating an updated image manifest")
575826 }
576827 pendingImage = pi
577828 }
578 manifest, _, err := pendingImage.Manifest(ctx)
579 if err != nil {
580 return nil, errors.Wrap(err, "Error reading manifest")
829 man, _, err := pendingImage.Manifest(ctx)
830 if err != nil {
831 return nil, "", errors.Wrap(err, "Error reading manifest")
581832 }
582833
583834 if err := ic.c.copyConfig(ctx, pendingImage); err != nil {
584 return nil, err
835 return nil, "", err
585836 }
586837
587838 ic.c.Printf("Writing manifest to image destination\n")
588 if err := ic.c.dest.PutManifest(ctx, manifest); err != nil {
589 return nil, errors.Wrap(err, "Error writing manifest")
590 }
591 return manifest, nil
839 manifestDigest, err := manifest.Digest(man)
840 if err != nil {
841 return nil, "", err
842 }
843 if instanceDigest != nil {
844 instanceDigest = &manifestDigest
845 }
846 if err := ic.c.dest.PutManifest(ctx, man, instanceDigest); err != nil {
847 return nil, "", errors.Wrap(err, "Error writing manifest")
848 }
849 return man, manifestDigest, nil
592850 }
593851
594852 // newProgressPool creates a *mpb.Progress and a cleanup function.
118118 }
119119 return manifest.MIMETypeIsMultiImage(mt), nil
120120 }
121
122 // determineListConversion takes the current MIME type of a list of manifests,
123 // the list of MIME types supported for a given destination, and a possible
124 // forced value, and returns the MIME type to which we should convert the list
125 // of manifests, whether we are converting to it or using it unmodified.
126 func (c *copier) determineListConversion(currentListMIMEType string, destSupportedMIMETypes []string, forcedListMIMEType string) (string, error) {
127 // If we're forcing it, we prefer the forced value over everything else.
128 if forcedListMIMEType != "" {
129 return forcedListMIMEType, nil
130 }
131 // If there's no list of supported types, then anything we support is expected to be supported.
132 if len(destSupportedMIMETypes) == 0 {
133 destSupportedMIMETypes = manifest.SupportedListMIMETypes
134 }
135 var selectedType string
136 for i := range destSupportedMIMETypes {
137 // The second priority is the first member of the list of acceptable types that is a list,
138 // but keep going in case current type occurs later in the list.
139 if selectedType == "" && manifest.MIMETypeIsMultiImage(destSupportedMIMETypes[i]) {
140 selectedType = destSupportedMIMETypes[i]
141 }
142 // The first priority is the current type, if it's in the list, since that lets us avoid a
143 // conversion that isn't strictly necessary.
144 if destSupportedMIMETypes[i] == currentListMIMEType {
145 selectedType = destSupportedMIMETypes[i]
146 }
147 }
148 if selectedType == "" {
149 return "", errors.Errorf("destination does not support any supported manifest list types (%v)", manifest.SupportedListMIMETypes)
150 }
151 // Done.
152 return selectedType, nil
153 }
202202 }{
203203 {manifest.DockerV2ListMediaType, true},
204204 {manifest.DockerV2Schema2MediaType, false},
205 {v1.MediaTypeImageManifest, false},
206 {v1.MediaTypeImageIndex, true},
205207 } {
206208 src := fakeImageSource(c.mt)
207209 res, err := isMultiImage(context.Background(), src)
198198 }
199199
200200 // PutManifest writes manifest to the destination.
201 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write the manifest for (when
202 // the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
203 // It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated
204 // by `manifest.Digest()`.
201205 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
202206 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
203207 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
204 func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte) error {
205 return ioutil.WriteFile(d.ref.manifestPath(), manifest, 0644)
206 }
207
208 func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
208 func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte, instanceDigest *digest.Digest) error {
209 return ioutil.WriteFile(d.ref.manifestPath(instanceDigest), manifest, 0644)
210 }
211
212 // PutSignatures writes a set of signatures to the destination.
213 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
214 // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
215 func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
209216 for i, sig := range signatures {
210 if err := ioutil.WriteFile(d.ref.signaturePath(i), sig, 0644); err != nil {
217 if err := ioutil.WriteFile(d.ref.signaturePath(i, instanceDigest), sig, 0644); err != nil {
211218 return err
212219 }
213220 }
218225 // WARNING: This does not have any transactional semantics:
219226 // - Uploaded data MAY be visible to others before Commit() is called
220227 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
221 func (d *dirImageDestination) Commit(ctx context.Context) error {
228 func (d *dirImageDestination) Commit(context.Context, types.UnparsedImage) error {
222229 return nil
223230 }
224231
88 "github.com/containers/image/v4/manifest"
99 "github.com/containers/image/v4/types"
1010 "github.com/opencontainers/go-digest"
11 "github.com/pkg/errors"
1211 )
1312
1413 type dirImageSource struct {
3736 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
3837 // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
3938 func (s *dirImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
40 if instanceDigest != nil {
41 return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
42 }
43 m, err := ioutil.ReadFile(s.ref.manifestPath())
39 m, err := ioutil.ReadFile(s.ref.manifestPath(instanceDigest))
4440 if err != nil {
4541 return nil, "", err
4642 }
7268 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
7369 // (e.g. if the source never returns manifest lists).
7470 func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
75 if instanceDigest != nil {
76 return nil, errors.Errorf(`Manifests lists are not supported by "dir:"`)
77 }
7871 signatures := [][]byte{}
7972 for i := 0; ; i++ {
80 signature, err := ioutil.ReadFile(s.ref.signaturePath(i))
73 signature, err := ioutil.ReadFile(s.ref.signaturePath(i, instanceDigest))
8174 if err != nil {
8275 if os.IsNotExist(err) {
8376 break
8982 return signatures, nil
9083 }
9184
92 // LayerInfosForCopy() returns updated layer info that should be used when copying, in preference to values in the manifest, if specified.
93 func (s *dirImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
85 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
86 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
87 // to read the image's layers.
88 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
89 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
90 // (e.g. if the source never returns manifest lists).
91 // The Digest field is guaranteed to be provided; Size may be -1.
92 // WARNING: The list may contain duplicates, and they are semantically relevant.
93 func (s *dirImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
9494 return nil, nil
9595 }
3131 defer os.RemoveAll(tmpDir)
3232
3333 man := []byte("test-manifest")
34 dest, err := ref.NewImageDestination(context.Background(), nil)
35 require.NoError(t, err)
36 defer dest.Close()
37 err = dest.PutManifest(context.Background(), man)
38 assert.NoError(t, err)
39 err = dest.Commit(context.Background())
34 list := []byte("test-manifest-list")
35 md, err := manifest.Digest(man)
36 require.NoError(t, err)
37 dest, err := ref.NewImageDestination(context.Background(), nil)
38 require.NoError(t, err)
39 defer dest.Close()
40 err = dest.PutManifest(context.Background(), man, &md)
41 assert.NoError(t, err)
42 err = dest.PutManifest(context.Background(), list, nil)
43 assert.NoError(t, err)
44 err = dest.Commit(context.Background(), nil)
4045 assert.NoError(t, err)
4146
4247 src, err := ref.NewImageSource(context.Background(), nil)
4348 require.NoError(t, err)
4449 defer src.Close()
4550 m, mt, err := src.GetManifest(context.Background(), nil)
51 assert.NoError(t, err)
52 assert.Equal(t, list, m)
53 assert.Equal(t, "", mt)
54
55 m, mt, err = src.GetManifest(context.Background(), &md)
4656 assert.NoError(t, err)
4757 assert.Equal(t, man, m)
4858 assert.Equal(t, "", mt)
49
50 // Non-default instances are not supported
51 md, err := manifest.Digest(man)
52 require.NoError(t, err)
53 _, _, err = src.GetManifest(context.Background(), &md)
54 assert.Error(t, err)
5559 }
5660
5761 func TestGetPutBlob(t *testing.T) {
6670 assert.Equal(t, types.PreserveOriginal, dest.DesiredLayerCompression())
6771 info, err := dest.PutBlob(context.Background(), bytes.NewReader(blob), types.BlobInfo{Digest: digest.Digest("sha256:digest-test"), Size: int64(9)}, cache, false)
6872 assert.NoError(t, err)
69 err = dest.Commit(context.Background())
73 err = dest.Commit(context.Background(), nil)
7074 assert.NoError(t, err)
7175 assert.Equal(t, int64(9), info.Size)
7276 assert.Equal(t, digest.FromBytes(blob), info.Digest)
125129 _, err = dest.PutBlob(context.Background(), reader, types.BlobInfo{Digest: blobDigest, Size: -1}, cache, false)
126130 assert.Error(t, err)
127131 assert.Contains(t, digestErrorString, err.Error())
128 err = dest.Commit(context.Background())
132 err = dest.Commit(context.Background(), nil)
129133 assert.NoError(t, err)
130134
131135 _, err = os.Lstat(blobPath)
147151 }
148152 err = dest.SupportsSignatures(context.Background())
149153 assert.NoError(t, err)
150 err = dest.PutManifest(context.Background(), man)
151 require.NoError(t, err)
152
153 err = dest.PutSignatures(context.Background(), signatures)
154 assert.NoError(t, err)
155 err = dest.Commit(context.Background())
154 err = dest.PutManifest(context.Background(), man, nil)
155 require.NoError(t, err)
156
157 err = dest.PutSignatures(context.Background(), signatures, nil)
158 listSignatures := [][]byte{
159 []byte("sig3"),
160 []byte("sig4"),
161 }
162 md, err := manifest.Digest(man)
163 require.NoError(t, err)
164
165 err = dest.SupportsSignatures(context.Background())
166 assert.NoError(t, err)
167 err = dest.PutManifest(context.Background(), man, nil)
168 require.NoError(t, err)
169 err = dest.PutManifest(context.Background(), man, &md)
170 require.NoError(t, err)
171
172 err = dest.PutSignatures(context.Background(), listSignatures, nil)
173 assert.NoError(t, err)
174 err = dest.PutSignatures(context.Background(), signatures, &md)
175 assert.NoError(t, err)
176 err = dest.Commit(context.Background(), nil)
156177 assert.NoError(t, err)
157178
158179 src, err := ref.NewImageSource(context.Background(), nil)
160181 defer src.Close()
161182 sigs, err := src.GetSignatures(context.Background(), nil)
162183 assert.NoError(t, err)
184 assert.Equal(t, listSignatures, sigs)
185
186 sigs, err = src.GetSignatures(context.Background(), &md)
187 assert.NoError(t, err)
163188 assert.Equal(t, signatures, sigs)
164
165 // Non-default instances are not supported
166 md, err := manifest.Digest(man)
167 require.NoError(t, err)
168 _, err = src.GetSignatures(context.Background(), &md)
169 assert.Error(t, err)
170189 }
171190
172191 func TestSourceReference(t *testing.T) {
165165 }
166166
167167 // manifestPath returns a path for the manifest within a directory using our conventions.
168 func (ref dirReference) manifestPath() string {
168 func (ref dirReference) manifestPath(instanceDigest *digest.Digest) string {
169 if instanceDigest != nil {
170 return filepath.Join(ref.path, instanceDigest.Encoded()+".manifest.json")
171 }
169172 return filepath.Join(ref.path, "manifest.json")
170173 }
171174
172175 // layerPath returns a path for a layer tarball within a directory using our conventions.
173176 func (ref dirReference) layerPath(digest digest.Digest) string {
174177 // FIXME: Should we keep the digest identification?
175 return filepath.Join(ref.path, digest.Hex())
178 return filepath.Join(ref.path, digest.Encoded())
176179 }
177180
178181 // signaturePath returns a path for a signature within a directory using our conventions.
179 func (ref dirReference) signaturePath(index int) string {
182 func (ref dirReference) signaturePath(index int, instanceDigest *digest.Digest) string {
183 if instanceDigest != nil {
184 return filepath.Join(ref.path, fmt.Sprintf(instanceDigest.Encoded()+".signature-%d", index+1))
185 }
180186 return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1))
181187 }
182188
88
99 _ "github.com/containers/image/v4/internal/testing/explicitfilepath-tmpdir"
1010 "github.com/containers/image/v4/types"
11 digest "github.com/opencontainers/go-digest"
1112 "github.com/stretchr/testify/assert"
1213 "github.com/stretchr/testify/require"
1314 )
156157 defer dest.Close()
157158 mFixture, err := ioutil.ReadFile("../manifest/fixtures/v2s1.manifest.json")
158159 require.NoError(t, err)
159 err = dest.PutManifest(context.Background(), mFixture)
160 assert.NoError(t, err)
161 err = dest.Commit(context.Background())
160 err = dest.PutManifest(context.Background(), mFixture, nil)
161 assert.NoError(t, err)
162 err = dest.Commit(context.Background(), nil)
162163 assert.NoError(t, err)
163164
164165 img, err := ref.NewImage(context.Background(), nil)
173174 dest, err := ref.NewImageDestination(context.Background(), nil)
174175 require.NoError(t, err)
175176 defer dest.Close()
176 err = dest.PutManifest(context.Background(), []byte(`{"schemaVersion":1}`))
177 assert.NoError(t, err)
178 err = dest.Commit(context.Background())
177 err = dest.PutManifest(context.Background(), []byte(`{"schemaVersion":1}`), nil)
178 assert.NoError(t, err)
179 err = dest.Commit(context.Background(), nil)
179180 assert.NoError(t, err)
180181
181182 _, err = ref.NewImage(context.Background(), nil)
206207 }
207208
208209 func TestReferenceManifestPath(t *testing.T) {
209 ref, tmpDir := refToTempDir(t)
210 defer os.RemoveAll(tmpDir)
211 dirRef, ok := ref.(dirReference)
212 require.True(t, ok)
213 assert.Equal(t, tmpDir+"/manifest.json", dirRef.manifestPath())
210 dhex := digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
211
212 ref, tmpDir := refToTempDir(t)
213 defer os.RemoveAll(tmpDir)
214 dirRef, ok := ref.(dirReference)
215 require.True(t, ok)
216 assert.Equal(t, tmpDir+"/manifest.json", dirRef.manifestPath(nil))
217 assert.Equal(t, tmpDir+"/"+dhex.Encoded()+".manifest.json", dirRef.manifestPath(&dhex))
214218 }
215219
216220 func TestReferenceLayerPath(t *testing.T) {
224228 }
225229
226230 func TestReferenceSignaturePath(t *testing.T) {
227 ref, tmpDir := refToTempDir(t)
228 defer os.RemoveAll(tmpDir)
229 dirRef, ok := ref.(dirReference)
230 require.True(t, ok)
231 assert.Equal(t, tmpDir+"/signature-1", dirRef.signaturePath(0))
232 assert.Equal(t, tmpDir+"/signature-10", dirRef.signaturePath(9))
231 dhex := digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
232
233 ref, tmpDir := refToTempDir(t)
234 defer os.RemoveAll(tmpDir)
235 dirRef, ok := ref.(dirReference)
236 require.True(t, ok)
237 assert.Equal(t, tmpDir+"/signature-1", dirRef.signaturePath(0, nil))
238 assert.Equal(t, tmpDir+"/signature-10", dirRef.signaturePath(9, nil))
239 assert.Equal(t, tmpDir+"/"+dhex.Encoded()+".signature-1", dirRef.signaturePath(0, &dhex))
240 assert.Equal(t, tmpDir+"/"+dhex.Encoded()+".signature-10", dirRef.signaturePath(9, &dhex))
233241 }
234242
235243 func TestReferenceVersionPath(t *testing.T) {
6666 // WARNING: This does not have any transactional semantics:
6767 // - Uploaded data MAY be visible to others before Commit() is called
6868 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
69 func (d *archiveImageDestination) Commit(ctx context.Context) error {
69 func (d *archiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
7070 return d.Destination.Commit(ctx)
7171 }
3232 func (s *archiveImageSource) Reference() types.ImageReference {
3333 return s.ref
3434 }
35
36 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
37 func (s *archiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
38 return nil, nil
39 }
123123 // WARNING: This does not have any transactional semantics:
124124 // - Uploaded data MAY be visible to others before Commit() is called
125125 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
126 func (d *daemonImageDestination) Commit(ctx context.Context) error {
126 func (d *daemonImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
127127 logrus.Debugf("docker-daemon: Closing tar stream")
128128 if err := d.Destination.Commit(ctx); err != nil {
129129 return err
5454 func (s *daemonImageSource) Reference() types.ImageReference {
5555 return s.ref
5656 }
57
58 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
59 func (s *daemonImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
60 return nil, nil
61 }
6060 return []string{
6161 imgspecv1.MediaTypeImageManifest,
6262 manifest.DockerV2Schema2MediaType,
63 imgspecv1.MediaTypeImageIndex,
64 manifest.DockerV2ListMediaType,
6365 manifest.DockerV2Schema1SignedMediaType,
6466 manifest.DockerV2Schema1MediaType,
6567 }
342344 }
343345
344346 // PutManifest writes manifest to the destination.
347 // When the primary manifest is a manifest list, if instanceDigest is nil, we're saving the list
348 // itself, else instanceDigest contains a digest of the specific manifest instance to overwrite the
349 // manifest for; when the primary manifest is not a manifest list, instanceDigest should always be nil.
345350 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
346351 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
347352 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
348 func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) error {
349 digest, err := manifest.Digest(m)
350 if err != nil {
351 return err
352 }
353 d.manifestDigest = digest
354
355 refTail, err := d.ref.tagOrDigest()
356 if err != nil {
357 return err
358 }
353 func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error {
354 refTail := ""
355 if instanceDigest != nil {
356 // If the instanceDigest is provided, then use it as the refTail, because the reference,
357 // whether it includes a tag or a digest, refers to the list as a whole, and not this
358 // particular instance.
359 refTail = instanceDigest.String()
360 // Double-check that the manifest we've been given matches the digest we've been given.
361 matches, err := manifest.MatchesDigest(m, *instanceDigest)
362 if err != nil {
363 return errors.Wrapf(err, "error digesting manifest in PutManifest")
364 }
365 if !matches {
366 manifestDigest, merr := manifest.Digest(m)
367 if merr != nil {
368 return errors.Wrapf(err, "Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%v attempting to compute it)", instanceDigest.String(), merr)
369 }
370 return errors.Errorf("Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%q)", instanceDigest.String(), manifestDigest.String())
371 }
372 } else {
373 // Compute the digest of the main manifest, or the list if it's a list, so that we
374 // have a digest value to use if we're asked to save a signature for the manifest.
375 digest, err := manifest.Digest(m)
376 if err != nil {
377 return err
378 }
379 d.manifestDigest = digest
380 // The refTail should be either a digest (which we expect to match the value we just
381 // computed) or a tag name.
382 refTail, err = d.ref.tagOrDigest()
383 if err != nil {
384 return err
385 }
386 }
387
359388 path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), refTail)
360389
361390 headers := map[string][]string{}
415444 }
416445 }
417446
418 func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
447 // PutSignatures uploads a set of signatures to the relevant lookaside or API extension point.
448 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to upload the signatures for (when
449 // the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
450 func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
419451 // Do not fail if we don’t really need to support signatures.
420452 if len(signatures) == 0 {
421453 return nil
422454 }
455 if instanceDigest == nil {
456 if d.manifestDigest.String() == "" {
457 // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
458 return errors.Errorf("Unknown manifest digest, can't add signatures")
459 }
460 instanceDigest = &d.manifestDigest
461 }
462
423463 if err := d.c.detectProperties(ctx); err != nil {
424464 return err
425465 }
426466 switch {
427467 case d.c.signatureBase != nil:
428 return d.putSignaturesToLookaside(signatures)
468 return d.putSignaturesToLookaside(signatures, instanceDigest)
429469 case d.c.supportsSignatures:
430 return d.putSignaturesToAPIExtension(ctx, signatures)
470 return d.putSignaturesToAPIExtension(ctx, signatures, instanceDigest)
431471 default:
432472 return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
433473 }
435475
436476 // putSignaturesToLookaside implements PutSignatures() from the lookaside location configured in s.c.signatureBase,
437477 // which is not nil.
438 func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) error {
478 func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, instanceDigest *digest.Digest) error {
439479 // FIXME? This overwrites files one at a time, definitely not atomic.
440480 // A failure when updating signatures with a reordered copy could lose some of them.
441481
444484 return nil
445485 }
446486
447 if d.manifestDigest.String() == "" {
448 // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
449 return errors.Errorf("Unknown manifest digest, can't add signatures")
450 }
451
452487 // NOTE: Keep this in sync with docs/signature-protocols.md!
453488 for i, signature := range signatures {
454 url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
489 url := signatureStorageURL(d.c.signatureBase, *instanceDigest, i)
455490 if url == nil {
456491 return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
457492 }
466501 // is enough for dockerImageSource to stop looking for other signatures, so that
467502 // is sufficient.
468503 for i := len(signatures); ; i++ {
469 url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
504 url := signatureStorageURL(d.c.signatureBase, *instanceDigest, i)
470505 if url == nil {
471506 return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
472507 }
526561 }
527562
528563 // putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension.
529 func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte) error {
564 func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
530565 // Skip dealing with the manifest digest, or reading the old state, if not necessary.
531566 if len(signatures) == 0 {
532567 return nil
533568 }
534569
535 if d.manifestDigest.String() == "" {
536 // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
537 return errors.Errorf("Unknown manifest digest, can't add signatures")
538 }
539
540570 // Because image signatures are a shared resource in Atomic Registry, the default upload
541571 // always adds signatures. Eventually we should also allow removing signatures,
542572 // but the X-Registry-Supports-Signatures API extension does not support that yet.
543573
544 existingSignatures, err := d.c.getExtensionsSignatures(ctx, d.ref, d.manifestDigest)
574 existingSignatures, err := d.c.getExtensionsSignatures(ctx, d.ref, *instanceDigest)
545575 if err != nil {
546576 return err
547577 }
566596 if err != nil || n != 16 {
567597 return errors.Wrapf(err, "Error generating random signature len %d", n)
568598 }
569 signatureName = fmt.Sprintf("%s@%032x", d.manifestDigest.String(), randBytes)
599 signatureName = fmt.Sprintf("%s@%032x", instanceDigest.String(), randBytes)
570600 if _, ok := existingSigNames[signatureName]; !ok {
571601 break
572602 }
605635 // WARNING: This does not have any transactional semantics:
606636 // - Uploaded data MAY be visible to others before Commit() is called
607637 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
608 func (d *dockerImageDestination) Commit(ctx context.Context) error {
638 func (d *dockerImageDestination) Commit(context.Context, types.UnparsedImage) error {
609639 return nil
610640 }
102102 return nil
103103 }
104104
105 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
106 func (s *dockerImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
105 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
106 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
107 // to read the image's layers.
108 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
109 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
110 // (e.g. if the source never returns manifest lists).
111 // The Digest field is guaranteed to be provided; Size may be -1.
112 // WARNING: The list may contain duplicates, and they are semantically relevant.
113 func (s *dockerImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
107114 return nil, nil
108115 }
109116
194194 }
195195
196196 // PutManifest writes manifest to the destination.
197 // The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
198 // there can be no secondary manifests.
197199 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
198200 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
199201 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
200 func (d *Destination) PutManifest(ctx context.Context, m []byte) error {
202 func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error {
203 if instanceDigest != nil {
204 return errors.New(`Manifest lists are not supported for docker tar files`)
205 }
201206 // We do not bother with types.ManifestTypeRejectedError; our .SupportedManifestMIMETypes() above is already providing only one alternative,
202207 // so the caller trying a different manifest kind would be pointless.
203208 var man manifest.Schema2
389394 return nil
390395 }
391396
392 // PutSignatures adds the given signatures to the docker tarfile (currently not
393 // supported). MUST be called after PutManifest (signatures reference manifest
394 // contents)
395 func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte) error {
397 // PutSignatures would add the given signatures to the docker tarfile (currently not supported).
398 // The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
399 // there can be no secondary manifests. MUST be called after PutManifest (signatures reference manifest contents).
400 func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
401 if instanceDigest != nil {
402 return errors.Errorf(`Manifest lists are not supported for docker tar files`)
403 }
396404 if len(signatures) != 0 {
397405 return errors.Errorf("Storing signatures for docker tar files is not supported")
398406 }
348348 // It may use a remote (= slow) service.
349349 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
350350 // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
351 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
352 // as the primary manifest can not be a list, so there can be no secondary instances.
351353 func (s *Source) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
352354 if instanceDigest != nil {
353355 // How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType.
354 return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
356 return nil, "", errors.New(`Manifest lists are not supported by "docker-daemon:"`)
355357 }
356358 if s.generatedManifest == nil {
357359 if err := s.ensureCachedDataIsPresent(); err != nil {
465467 }
466468
467469 // GetSignatures returns the image's signatures. It may use a remote (= slow) service.
468 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
469 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
470 // (e.g. if the source never returns manifest lists).
470 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
471 // as there can be no secondary manifests.
471472 func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
472473 if instanceDigest != nil {
473474 // How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType.
475476 }
476477 return [][]byte{}, nil
477478 }
479
480 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
481 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
482 // to read the image's layers.
483 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
484 // as the primary manifest can not be a list, so there can be no secondary manifests.
485 // The Digest field is guaranteed to be provided; Size may be -1.
486 // WARNING: The list may contain duplicates, and they are semantically relevant.
487 func (s *Source) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
488 return nil, nil
489 }
11
22 import (
33 "context"
4 "encoding/json"
5 "fmt"
6 "runtime"
74
85 "github.com/containers/image/v4/manifest"
96 "github.com/containers/image/v4/types"
10 "github.com/opencontainers/go-digest"
117 "github.com/pkg/errors"
128 )
139
14 type platformSpec struct {
15 Architecture string `json:"architecture"`
16 OS string `json:"os"`
17 OSVersion string `json:"os.version,omitempty"`
18 OSFeatures []string `json:"os.features,omitempty"`
19 Variant string `json:"variant,omitempty"`
20 Features []string `json:"features,omitempty"` // removed in OCI
21 }
22
23 // A manifestDescriptor references a platform-specific manifest.
24 type manifestDescriptor struct {
25 manifest.Schema2Descriptor
26 Platform platformSpec `json:"platform"`
27 }
28
29 type manifestList struct {
30 SchemaVersion int `json:"schemaVersion"`
31 MediaType string `json:"mediaType"`
32 Manifests []manifestDescriptor `json:"manifests"`
33 }
34
35 // chooseDigestFromManifestList parses blob as a schema2 manifest list,
36 // and returns the digest of the image appropriate for the current environment.
37 func chooseDigestFromManifestList(sys *types.SystemContext, blob []byte) (digest.Digest, error) {
38 wantedArch := runtime.GOARCH
39 if sys != nil && sys.ArchitectureChoice != "" {
40 wantedArch = sys.ArchitectureChoice
10 func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
11 list, err := manifest.Schema2ListFromManifest(manblob)
12 if err != nil {
13 return nil, errors.Wrapf(err, "Error parsing schema2 manifest list")
4114 }
42 wantedOS := runtime.GOOS
43 if sys != nil && sys.OSChoice != "" {
44 wantedOS = sys.OSChoice
45 }
46
47 list := manifestList{}
48 if err := json.Unmarshal(blob, &list); err != nil {
49 return "", err
50 }
51 for _, d := range list.Manifests {
52 if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
53 return d.Digest, nil
54 }
55 }
56 return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS)
57 }
58
59 func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
60 targetManifestDigest, err := chooseDigestFromManifestList(sys, manblob)
15 targetManifestDigest, err := list.ChooseInstance(sys)
6116 if err != nil {
62 return nil, err
17 return nil, errors.Wrapf(err, "Error choosing image instance")
6318 }
6419 manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest)
6520 if err != nil {
66 return nil, err
21 return nil, errors.Wrapf(err, "Error loading manifest for target platform")
6722 }
6823
6924 matches, err := manifest.MatchesDigest(manblob, targetManifestDigest)
7126 return nil, errors.Wrap(err, "Error computing manifest digest")
7227 }
7328 if !matches {
74 return nil, errors.Errorf("Manifest image does not match selected manifest digest %s", targetManifestDigest)
29 return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
7530 }
7631
7732 return manifestInstanceFromBlob(ctx, sys, src, manblob, mt)
7833 }
79
80 // ChooseManifestInstanceFromManifestList returns a digest of a manifest appropriate
81 // for the current system from the manifest available from src.
82 func ChooseManifestInstanceFromManifestList(ctx context.Context, sys *types.SystemContext, src types.UnparsedImage) (digest.Digest, error) {
83 // For now this only handles manifest.DockerV2ListMediaType; we can generalize it later,
84 // probably along with manifest list editing.
85 blob, mt, err := src.Manifest(ctx)
86 if err != nil {
87 return "", err
88 }
89 if mt != manifest.DockerV2ListMediaType {
90 return "", fmt.Errorf("Internal error: Trying to select an image from a non-manifest-list manifest type %s", mt)
91 }
92 return chooseDigestFromManifestList(sys, blob)
93 }
+0
-44
image/docker_list_test.go less more
0 package image
1
2 import (
3 "bytes"
4 "io/ioutil"
5 "path/filepath"
6 "testing"
7
8 "github.com/containers/image/v4/types"
9 "github.com/opencontainers/go-digest"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 )
13
14 func TestChooseDigestFromManifestList(t *testing.T) {
15 manifest, err := ioutil.ReadFile(filepath.Join("fixtures", "schema2list.json"))
16 require.NoError(t, err)
17
18 // Match found
19 for arch, expected := range map[string]digest.Digest{
20 "amd64": "sha256:030fcb92e1487b18c974784dcc110a93147c9fc402188370fbfd17efabffc6af",
21 "s390x": "sha256:e5aa1b0a24620228b75382997a0977f609b3ca3a95533dafdef84c74cc8df642",
22 // There are several "arm" images with different variants;
23 // the current code returns the first match. NOTE: This is NOT an API promise.
24 "arm": "sha256:9142d97ef280a7953cf1a85716de49a24cc1dd62776352afad67e635331ff77a",
25 } {
26 digest, err := chooseDigestFromManifestList(&types.SystemContext{
27 ArchitectureChoice: arch,
28 OSChoice: "linux",
29 }, manifest)
30 require.NoError(t, err, arch)
31 assert.Equal(t, expected, digest)
32 }
33
34 // Invalid manifest list
35 _, err = chooseDigestFromManifestList(&types.SystemContext{
36 ArchitectureChoice: "amd64", OSChoice: "linux",
37 }, bytes.Join([][]byte{manifest, []byte("!INVALID")}, nil))
38 assert.Error(t, err)
39
40 // Not found
41 _, err = chooseDigestFromManifestList(&types.SystemContext{OSChoice: "Unmatched"}, manifest)
42 assert.Error(t, err)
43 }
4040 func (f unusedImageSource) GetSignatures(context.Context, *digest.Digest) ([][]byte, error) {
4141 panic("Unexpected call to a mock function")
4242 }
43 func (f unusedImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
43 func (f unusedImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
4444 panic("Unexpected call to a mock function")
4545 }
4646
429429 func (d *memoryImageDest) TryReusingBlob(context.Context, types.BlobInfo, types.BlobInfoCache, bool) (bool, types.BlobInfo, error) {
430430 panic("Unexpected call to a mock function")
431431 }
432 func (d *memoryImageDest) PutManifest(ctx context.Context, m []byte) error {
433 panic("Unexpected call to a mock function")
434 }
435 func (d *memoryImageDest) PutSignatures(ctx context.Context, signatures [][]byte) error {
436 panic("Unexpected call to a mock function")
437 }
438 func (d *memoryImageDest) Commit(ctx context.Context) error {
432 func (d *memoryImageDest) PutManifest(context.Context, []byte, *digest.Digest) error {
433 panic("Unexpected call to a mock function")
434 }
435 func (d *memoryImageDest) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
436 panic("Unexpected call to a mock function")
437 }
438 func (d *memoryImageDest) Commit(context.Context, types.UnparsedImage) error {
439439 panic("Unexpected call to a mock function")
440440 }
441441
0 {
1 "schemaVersion": 2,
2 "manifests": [
3 {
4 "mediaType": "application/vnd.oci.image.manifest.v1+json",
5 "size": 7143,
6 "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
7 "platform": {
8 "architecture": "ppc64le",
9 "os": "linux"
10 }
11 },
12 {
13 "mediaType": "application/vnd.oci.image.manifest.v1+json",
14 "size": 7682,
15 "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
16 "platform": {
17 "architecture": "amd64",
18 "os": "linux",
19 "os.features": [
20 "sse4"
21 ]
22 }
23 }
24 ],
25 "annotations": {
26 "com.example.key1": "value1",
27 "com.example.key2": "value2"
28 }
29 }
5757 return manifestSchema2FromManifest(src, manblob)
5858 case manifest.DockerV2ListMediaType:
5959 return manifestSchema2FromManifestList(ctx, sys, src, manblob)
60 case imgspecv1.MediaTypeImageIndex:
61 return manifestOCI1FromImageIndex(ctx, sys, src, manblob)
6062 default: // Note that this may not be reachable, manifest.NormalizedMIMEType has a default for unknown values.
6163 return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt)
6264 }
0 package image
1
2 import (
3 "context"
4
5 "github.com/containers/image/v4/manifest"
6 "github.com/containers/image/v4/types"
7 "github.com/pkg/errors"
8 )
9
10 func manifestOCI1FromImageIndex(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
11 index, err := manifest.OCI1IndexFromManifest(manblob)
12 if err != nil {
13 return nil, errors.Wrapf(err, "Error parsing OCI1 index")
14 }
15 targetManifestDigest, err := index.ChooseInstance(sys)
16 if err != nil {
17 return nil, errors.Wrapf(err, "Error choosing image instance")
18 }
19 manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest)
20 if err != nil {
21 return nil, errors.Wrapf(err, "Error loading manifest for target platform")
22 }
23
24 matches, err := manifest.MatchesDigest(manblob, targetManifestDigest)
25 if err != nil {
26 return nil, errors.Wrap(err, "Error computing manifest digest")
27 }
28 if !matches {
29 return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest)
30 }
31
32 return manifestInstanceFromBlob(ctx, sys, src, manblob, mt)
33 }
9999 }
100100
101101 func (i *sourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
102 return i.UnparsedImage.src.LayerInfosForCopy(ctx)
102 return i.UnparsedImage.src.LayerInfosForCopy(ctx, i.UnparsedImage.instanceDigest)
103103 }
0 package manifest
1
2 import (
3 "encoding/json"
4 "fmt"
5 "runtime"
6
7 "github.com/containers/image/v4/types"
8 "github.com/opencontainers/go-digest"
9 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
10 "github.com/pkg/errors"
11 )
12
13 // Schema2PlatformSpec describes the platform which a particular manifest is
14 // specialized for.
15 type Schema2PlatformSpec struct {
16 Architecture string `json:"architecture"`
17 OS string `json:"os"`
18 OSVersion string `json:"os.version,omitempty"`
19 OSFeatures []string `json:"os.features,omitempty"`
20 Variant string `json:"variant,omitempty"`
21 Features []string `json:"features,omitempty"` // removed in OCI
22 }
23
24 // Schema2ManifestDescriptor references a platform-specific manifest.
25 type Schema2ManifestDescriptor struct {
26 Schema2Descriptor
27 Platform Schema2PlatformSpec `json:"platform"`
28 }
29
30 // Schema2List is a list of platform-specific manifests.
31 type Schema2List struct {
32 SchemaVersion int `json:"schemaVersion"`
33 MediaType string `json:"mediaType"`
34 Manifests []Schema2ManifestDescriptor `json:"manifests"`
35 }
36
37 // MIMEType returns the MIME type of this particular manifest list.
38 func (list *Schema2List) MIMEType() string {
39 return list.MediaType
40 }
41
42 // Instances returns a slice of digests of the manifests that this list knows of.
43 func (list *Schema2List) Instances() []digest.Digest {
44 results := make([]digest.Digest, len(list.Manifests))
45 for i, m := range list.Manifests {
46 results[i] = m.Digest
47 }
48 return results
49 }
50
51 // Instance returns the ListUpdate of a particular instance in the list.
52 func (list *Schema2List) Instance(instanceDigest digest.Digest) (ListUpdate, error) {
53 for _, manifest := range list.Manifests {
54 if manifest.Digest == instanceDigest {
55 return ListUpdate{
56 Digest: manifest.Digest,
57 Size: manifest.Size,
58 MediaType: manifest.MediaType,
59 }, nil
60 }
61 }
62 return ListUpdate{}, errors.Errorf("unable to find instance %s passed to Schema2List.Instances", instanceDigest)
63 }
64
65 // UpdateInstances updates the sizes, digests, and media types of the manifests
66 // which the list catalogs.
67 func (list *Schema2List) UpdateInstances(updates []ListUpdate) error {
68 if len(updates) != len(list.Manifests) {
69 return errors.Errorf("incorrect number of update entries passed to Schema2List.UpdateInstances: expected %d, got %d", len(list.Manifests), len(updates))
70 }
71 for i := range updates {
72 if err := updates[i].Digest.Validate(); err != nil {
73 return errors.Wrapf(err, "update %d of %d passed to Schema2List.UpdateInstances contained an invalid digest", i+1, len(updates))
74 }
75 list.Manifests[i].Digest = updates[i].Digest
76 if updates[i].Size < 0 {
77 return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
78 }
79 list.Manifests[i].Size = updates[i].Size
80 if updates[i].MediaType == "" {
81 return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(updates), list.Manifests[i].MediaType)
82 }
83 if err := SupportedSchema2MediaType(updates[i].MediaType); err != nil && SupportedOCI1MediaType(updates[i].MediaType) != nil {
84 return errors.Wrapf(err, "update %d of %d passed to Schema2List.UpdateInstances had an unsupported media type (was %q): %q", i+1, len(updates), list.Manifests[i].MediaType, updates[i].MediaType)
85 }
86 list.Manifests[i].MediaType = updates[i].MediaType
87 }
88 return nil
89 }
90
91 // ChooseInstance parses blob as a schema2 manifest list, and returns the digest
92 // of the image which is appropriate for the current environment.
93 func (list *Schema2List) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
94 wantedArch := runtime.GOARCH
95 if ctx != nil && ctx.ArchitectureChoice != "" {
96 wantedArch = ctx.ArchitectureChoice
97 }
98 wantedOS := runtime.GOOS
99 if ctx != nil && ctx.OSChoice != "" {
100 wantedOS = ctx.OSChoice
101 }
102
103 for _, d := range list.Manifests {
104 if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
105 return d.Digest, nil
106 }
107 }
108 return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS)
109 }
110
111 // Serialize returns the list in a blob format.
112 // NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
113 func (list *Schema2List) Serialize() ([]byte, error) {
114 buf, err := json.Marshal(list)
115 if err != nil {
116 return nil, errors.Wrapf(err, "error marshaling Schema2List %#v", list)
117 }
118 return buf, nil
119 }
120
121 // Schema2ListFromComponents creates a Schema2 manifest list instance from the
122 // supplied data.
123 func Schema2ListFromComponents(components []Schema2ManifestDescriptor) *Schema2List {
124 list := Schema2List{
125 SchemaVersion: 2,
126 MediaType: DockerV2ListMediaType,
127 Manifests: make([]Schema2ManifestDescriptor, len(components)),
128 }
129 for i, component := range components {
130 m := Schema2ManifestDescriptor{
131 Schema2Descriptor{
132 MediaType: component.MediaType,
133 Size: component.Size,
134 Digest: component.Digest,
135 URLs: dupStringSlice(component.URLs),
136 },
137 Schema2PlatformSpec{
138 Architecture: component.Platform.Architecture,
139 OS: component.Platform.OS,
140 OSVersion: component.Platform.OSVersion,
141 OSFeatures: dupStringSlice(component.Platform.OSFeatures),
142 Variant: component.Platform.Variant,
143 Features: dupStringSlice(component.Platform.Features),
144 },
145 }
146 list.Manifests[i] = m
147 }
148 return &list
149 }
150
151 // Schema2ListClone creates a deep copy of the passed-in list.
152 func Schema2ListClone(list *Schema2List) *Schema2List {
153 return Schema2ListFromComponents(list.Manifests)
154 }
155
156 // ToOCI1Index returns the list encoded as an OCI1 index.
157 func (list *Schema2List) ToOCI1Index() (*OCI1Index, error) {
158 components := make([]imgspecv1.Descriptor, 0, len(list.Manifests))
159 for _, manifest := range list.Manifests {
160 converted := imgspecv1.Descriptor{
161 MediaType: manifest.MediaType,
162 Size: manifest.Size,
163 Digest: manifest.Digest,
164 URLs: dupStringSlice(manifest.URLs),
165 Platform: &imgspecv1.Platform{
166 OS: manifest.Platform.OS,
167 Architecture: manifest.Platform.Architecture,
168 OSFeatures: dupStringSlice(manifest.Platform.OSFeatures),
169 OSVersion: manifest.Platform.OSVersion,
170 Variant: manifest.Platform.Variant,
171 },
172 }
173 components = append(components, converted)
174 }
175 oci := OCI1IndexFromComponents(components, nil)
176 return oci, nil
177 }
178
179 // ToSchema2List returns the list encoded as a Schema2 list.
180 func (list *Schema2List) ToSchema2List() (*Schema2List, error) {
181 return Schema2ListClone(list), nil
182 }
183
184 // Schema2ListFromManifest creates a Schema2 manifest list instance from marshalled
185 // JSON, presumably generated by encoding a Schema2 manifest list.
186 func Schema2ListFromManifest(manifest []byte) (*Schema2List, error) {
187 list := Schema2List{
188 Manifests: []Schema2ManifestDescriptor{},
189 }
190 if err := json.Unmarshal(manifest, &list); err != nil {
191 return nil, errors.Wrapf(err, "error unmarshaling Schema2List %q", string(manifest))
192 }
193 return &list, nil
194 }
195
196 // Clone returns a deep copy of this list and its contents.
197 func (list *Schema2List) Clone() List {
198 return Schema2ListClone(list)
199 }
200
201 // ConvertToMIMEType converts the passed-in manifest list to a manifest
202 // list of the specified type.
203 func (list *Schema2List) ConvertToMIMEType(manifestMIMEType string) (List, error) {
204 switch normalized := NormalizedMIMEType(manifestMIMEType); normalized {
205 case DockerV2ListMediaType:
206 return list.Clone(), nil
207 case imgspecv1.MediaTypeImageIndex:
208 return list.ToOCI1Index()
209 case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType:
210 return nil, fmt.Errorf("Can not convert manifest list to MIME type %q, which is not a list type", manifestMIMEType)
211 default:
212 // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
213 return nil, fmt.Errorf("Unimplemented manifest list MIME type %s", manifestMIMEType)
214 }
215 }
0 package manifest
1
2 import (
3 "fmt"
4
5 "github.com/containers/image/v4/types"
6 digest "github.com/opencontainers/go-digest"
7 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
8 )
9
10 var (
11 // SupportedListMIMETypes is a list of the manifest list types that we know how to
12 // read/manipulate/write.
13 SupportedListMIMETypes = []string{
14 DockerV2ListMediaType,
15 imgspecv1.MediaTypeImageIndex,
16 }
17 )
18
19 // List is an interface for parsing, modifying lists of image manifests.
20 // Callers can either use this abstract interface without understanding the details of the formats,
21 // or instantiate a specific implementation (e.g. manifest.OCI1Index) and access the public members
22 // directly.
23 type List interface {
24 // MIMEType returns the MIME type of this particular manifest list.
25 MIMEType() string
26
27 // Instances returns a list of the manifests that this list knows of, other than its own.
28 Instances() []digest.Digest
29
30 // Update information about the list's instances. The length of the passed-in slice must
31 // match the length of the list of instances which the list already contains, and every field
32 // must be specified.
33 UpdateInstances([]ListUpdate) error
34
35 // Instance returns the size and MIME type of a particular instance in the list.
36 Instance(digest.Digest) (ListUpdate, error)
37
38 // ChooseInstance selects which manifest is most appropriate for the platform described by the
39 // SystemContext, or for the current platform if the SystemContext doesn't specify any details.
40 ChooseInstance(ctx *types.SystemContext) (digest.Digest, error)
41
42 // Serialize returns the list in a blob format.
43 // NOTE: Serialize() does not in general reproduce the original blob if this object was loaded
44 // from, even if no modifications were made!
45 Serialize() ([]byte, error)
46
47 // ConvertToMIMEType returns the list rebuilt to the specified MIME type, or an error.
48 ConvertToMIMEType(mimeType string) (List, error)
49
50 // Clone returns a deep copy of this list and its contents.
51 Clone() List
52 }
53
54 // ListUpdate includes the fields which a List's UpdateInstances() method will modify.
55 type ListUpdate struct {
56 Digest digest.Digest
57 Size int64
58 MediaType string
59 }
60
61 // dupStringSlice returns a deep copy of a slice of strings, or nil if the
62 // source slice is empty.
63 func dupStringSlice(list []string) []string {
64 if len(list) == 0 {
65 return nil
66 }
67 dup := make([]string, len(list))
68 for i := range list {
69 dup[i] = list[i]
70 }
71 return dup
72 }
73
74 // dupStringStringMap returns a deep copy of a map[string]string, or nil if the
75 // passed-in map is nil or has no keys.
76 func dupStringStringMap(m map[string]string) map[string]string {
77 if len(m) == 0 {
78 return nil
79 }
80 result := make(map[string]string)
81 for k, v := range m {
82 result[k] = v
83 }
84 return result
85 }
86
87 // ListFromBlob parses a list of manifests.
88 func ListFromBlob(manifest []byte, manifestMIMEType string) (List, error) {
89 normalized := NormalizedMIMEType(manifestMIMEType)
90 switch normalized {
91 case DockerV2ListMediaType:
92 return Schema2ListFromManifest(manifest)
93 case imgspecv1.MediaTypeImageIndex:
94 return OCI1IndexFromManifest(manifest)
95 case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType:
96 return nil, fmt.Errorf("Treating single images as manifest lists is not implemented")
97 }
98 return nil, fmt.Errorf("Unimplemented manifest list MIME type %s (normalized as %s)", manifestMIMEType, normalized)
99 }
100
101 // ConvertListToMIMEType converts the passed-in manifest list to a manifest
102 // list of the specified type.
103 func ConvertListToMIMEType(list List, manifestMIMEType string) (List, error) {
104 return list.ConvertToMIMEType(manifestMIMEType)
105 }
0 package manifest
1
2 import (
3 "io/ioutil"
4 "path/filepath"
5 "testing"
6
7 "github.com/containers/image/v4/types"
8 digest "github.com/opencontainers/go-digest"
9 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 )
13
14 func pare(m List) {
15 if impl, ok := m.(*OCI1Index); ok {
16 impl.Annotations = nil
17 }
18 if impl, ok := m.(*Schema2List); ok {
19 for i := range impl.Manifests {
20 impl.Manifests[i].Platform.Features = nil
21 }
22 }
23 return
24 }
25
26 func TestParseLists(t *testing.T) {
27 cases := []struct {
28 path string
29 mimeType string
30 }{
31 {"ociv1.image.index.json", imgspecv1.MediaTypeImageIndex},
32 {"v2list.manifest.json", DockerV2ListMediaType},
33 }
34 for _, c := range cases {
35 manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
36 require.NoError(t, err, "error reading file %q", filepath.Join("fixtures", c.path))
37 assert.Equal(t, GuessMIMEType(manifest), c.mimeType)
38
39 _, err = FromBlob(manifest, c.mimeType)
40 require.Error(t, err, "manifest list %q should not parse as single images", c.path)
41
42 m, err := ListFromBlob(manifest, c.mimeType)
43 require.NoError(t, err, "manifest list %q should parse as list types", c.path)
44 assert.Equal(t, m.MIMEType(), c.mimeType, "manifest %q is not of the expected MIME type", c.path)
45
46 clone := m.Clone()
47 assert.Equal(t, clone, m, "manifest %q is missing some fields after being cloned", c.path)
48
49 pare(m)
50
51 index, err := m.ConvertToMIMEType(imgspecv1.MediaTypeImageIndex)
52 require.NoError(t, err, "error converting %q to an OCI1Index", c.path)
53
54 list, err := m.ConvertToMIMEType(DockerV2ListMediaType)
55 require.NoError(t, err, "error converting %q to an Schema2List", c.path)
56
57 index2, err := list.ConvertToMIMEType(imgspecv1.MediaTypeImageIndex)
58 assert.Equal(t, index, index2, "index %q lost data in conversion", c.path)
59
60 list2, err := index.ConvertToMIMEType(DockerV2ListMediaType)
61 assert.Equal(t, list, list2, "list %q lost data in conversion", c.path)
62 }
63 }
64
65 func TestChooseInstance(t *testing.T) {
66 for _, manifestList := range []struct {
67 listFile string
68 matchedInstances map[string]digest.Digest
69 unmatchedInstances []string
70 }{
71 {
72 listFile: "schema2list.json",
73 matchedInstances: map[string]digest.Digest{
74 "amd64": "sha256:030fcb92e1487b18c974784dcc110a93147c9fc402188370fbfd17efabffc6af",
75 "s390x": "sha256:e5aa1b0a24620228b75382997a0977f609b3ca3a95533dafdef84c74cc8df642",
76 // There are several "arm" images with different variants;
77 // the current code returns the first match. NOTE: This is NOT an API promise.
78 "arm": "sha256:9142d97ef280a7953cf1a85716de49a24cc1dd62776352afad67e635331ff77a",
79 },
80 unmatchedInstances: []string{
81 "unmatched",
82 },
83 },
84 {
85 listFile: "oci1index.json",
86 matchedInstances: map[string]digest.Digest{
87 "amd64": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
88 "ppc64le": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
89 },
90 unmatchedInstances: []string{
91 "unmatched",
92 },
93 },
94 } {
95 man, err := ioutil.ReadFile(filepath.Join("..", "image", "fixtures", manifestList.listFile))
96 require.NoError(t, err)
97 rawManifest := man
98 list, err := ListFromBlob(rawManifest, GuessMIMEType(rawManifest))
99 require.NoError(t, err)
100 // Match found
101 for arch, expected := range manifestList.matchedInstances {
102 digest, err := list.ChooseInstance(&types.SystemContext{
103 ArchitectureChoice: arch,
104 OSChoice: "linux",
105 })
106 require.NoError(t, err, arch)
107 assert.Equal(t, expected, digest)
108 }
109 // Not found
110 for _, arch := range manifestList.unmatchedInstances {
111 _, err := list.ChooseInstance(&types.SystemContext{
112 ArchitectureChoice: arch,
113 OSChoice: "linux",
114 })
115 assert.Error(t, err)
116 }
117 }
118 }
5151 DockerV2Schema1SignedMediaType,
5252 DockerV2Schema1MediaType,
5353 DockerV2ListMediaType,
54 imgspecv1.MediaTypeImageIndex,
5455 }
5556
5657 // Manifest is an interface for parsing, modifying image manifests in isolation.
139140 if err := json.Unmarshal(manifest, &ociIndex); err != nil {
140141 return ""
141142 }
142 if len(ociIndex.Manifests) != 0 && ociIndex.Manifests[0].MediaType == imgspecv1.MediaTypeImageManifest {
143 return imgspecv1.MediaTypeImageIndex
143 if len(ociIndex.Manifests) != 0 {
144 if ociMan.Config.MediaType == "" {
145 return imgspecv1.MediaTypeImageIndex
146 }
147 return ociMan.Config.MediaType
144148 }
145149 return DockerV2Schema2MediaType
146150 }
198202
199203 // MIMETypeIsMultiImage returns true if mimeType is a list of images
200204 func MIMETypeIsMultiImage(mimeType string) bool {
201 return mimeType == DockerV2ListMediaType
205 return mimeType == DockerV2ListMediaType || mimeType == imgspecv1.MediaTypeImageIndex
202206 }
203207
204208 // NormalizedMIMEType returns the effective MIME type of a manifest MIME type returned by a server,
212216 return DockerV2Schema1SignedMediaType
213217 case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType,
214218 imgspecv1.MediaTypeImageManifest,
219 imgspecv1.MediaTypeImageIndex,
215220 DockerV2Schema2MediaType,
216221 DockerV2ListMediaType:
217222 return input
231236
232237 // FromBlob returns a Manifest instance for the specified manifest blob and the corresponding MIME type
233238 func FromBlob(manblob []byte, mt string) (Manifest, error) {
234 switch NormalizedMIMEType(mt) {
239 nmt := NormalizedMIMEType(mt)
240 switch nmt {
235241 case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType:
236242 return Schema1FromManifest(manblob)
237243 case imgspecv1.MediaTypeImageManifest:
238244 return OCI1FromManifest(manblob)
239245 case DockerV2Schema2MediaType:
240246 return Schema2FromManifest(manblob)
241 case DockerV2ListMediaType:
247 case DockerV2ListMediaType, imgspecv1.MediaTypeImageIndex:
242248 return nil, fmt.Errorf("Treating manifest lists as individual manifests is not implemented")
243 default: // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
244 return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt)
245 }
249 }
250 // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
251 return nil, fmt.Errorf("Unimplemented manifest MIME type %s (normalized as %s)", mt, nmt)
246252 }
247253
248254 // layerInfosToStrings converts a list of layer infos, presumably obtained from a Manifest.LayerInfos()
133133 {DockerV2Schema1MediaType, false},
134134 {DockerV2Schema1SignedMediaType, false},
135135 {DockerV2Schema2MediaType, false},
136 {imgspecv1.MediaTypeImageIndex, true},
137 {imgspecv1.MediaTypeImageManifest, false},
136138 } {
137139 res := MIMETypeIsMultiImage(c.mt)
138140 assert.Equal(t, c.expected, res, c.mt)
0 package manifest
1
2 import (
3 "encoding/json"
4 "fmt"
5 "runtime"
6
7 "github.com/containers/image/v4/types"
8 "github.com/opencontainers/go-digest"
9 imgspec "github.com/opencontainers/image-spec/specs-go"
10 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
11 "github.com/pkg/errors"
12 )
13
14 // OCI1Index is just an alias for the OCI index type, but one which we can
15 // provide methods for.
16 type OCI1Index struct {
17 imgspecv1.Index
18 }
19
20 // MIMEType returns the MIME type of this particular manifest index.
21 func (index *OCI1Index) MIMEType() string {
22 return imgspecv1.MediaTypeImageIndex
23 }
24
25 // Instances returns a slice of digests of the manifests that this index knows of.
26 func (index *OCI1Index) Instances() []digest.Digest {
27 results := make([]digest.Digest, len(index.Manifests))
28 for i, m := range index.Manifests {
29 results[i] = m.Digest
30 }
31 return results
32 }
33
34 // Instance returns the ListUpdate of a particular instance in the index.
35 func (index *OCI1Index) Instance(instanceDigest digest.Digest) (ListUpdate, error) {
36 for _, manifest := range index.Manifests {
37 if manifest.Digest == instanceDigest {
38 return ListUpdate{
39 Digest: manifest.Digest,
40 Size: manifest.Size,
41 MediaType: manifest.MediaType,
42 }, nil
43 }
44 }
45 return ListUpdate{}, errors.Errorf("unable to find instance %s in OCI1Index", instanceDigest)
46 }
47
48 // UpdateInstances updates the sizes, digests, and media types of the manifests
49 // which the list catalogs.
50 func (index *OCI1Index) UpdateInstances(updates []ListUpdate) error {
51 if len(updates) != len(index.Manifests) {
52 return errors.Errorf("incorrect number of update entries passed to OCI1Index.UpdateInstances: expected %d, got %d", len(index.Manifests), len(updates))
53 }
54 for i := range updates {
55 if err := updates[i].Digest.Validate(); err != nil {
56 return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances contained an invalid digest", i+1, len(updates))
57 }
58 index.Manifests[i].Digest = updates[i].Digest
59 if updates[i].Size < 0 {
60 return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
61 }
62 index.Manifests[i].Size = updates[i].Size
63 if updates[i].MediaType == "" {
64 return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(updates), index.Manifests[i].MediaType)
65 }
66 if err := SupportedOCI1MediaType(updates[i].MediaType); err != nil && SupportedSchema2MediaType(updates[i].MediaType) != nil && updates[i].MediaType != imgspecv1.MediaTypeImageIndex {
67 return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances had an unsupported media type (was %q): %q", i+1, len(updates), index.Manifests[i].MediaType, updates[i].MediaType)
68 }
69 index.Manifests[i].MediaType = updates[i].MediaType
70 }
71 return nil
72 }
73
74 // ChooseInstance parses blob as an oci v1 manifest index, and returns the digest
75 // of the image which is appropriate for the current environment.
76 func (index *OCI1Index) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
77 wantedArch := runtime.GOARCH
78 if ctx != nil && ctx.ArchitectureChoice != "" {
79 wantedArch = ctx.ArchitectureChoice
80 }
81 wantedOS := runtime.GOOS
82 if ctx != nil && ctx.OSChoice != "" {
83 wantedOS = ctx.OSChoice
84 }
85
86 for _, d := range index.Manifests {
87 if d.Platform != nil && d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
88 return d.Digest, nil
89 }
90 }
91 for _, d := range index.Manifests {
92 if d.Platform == nil {
93 return d.Digest, nil
94 }
95 }
96 return "", fmt.Errorf("no image found in image index for architecture %s, OS %s", wantedArch, wantedOS)
97 }
98
99 // Serialize returns the index in a blob format.
100 // NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
101 func (index *OCI1Index) Serialize() ([]byte, error) {
102 buf, err := json.Marshal(index)
103 if err != nil {
104 return nil, errors.Wrapf(err, "error marshaling OCI1Index %#v", index)
105 }
106 return buf, nil
107 }
108
109 // OCI1IndexFromComponents creates an OCI1 image index instance from the
110 // supplied data.
111 func OCI1IndexFromComponents(components []imgspecv1.Descriptor, annotations map[string]string) *OCI1Index {
112 index := OCI1Index{
113 imgspecv1.Index{
114 Versioned: imgspec.Versioned{SchemaVersion: 2},
115 Manifests: make([]imgspecv1.Descriptor, len(components)),
116 Annotations: dupStringStringMap(annotations),
117 },
118 }
119 for i, component := range components {
120 var platform *imgspecv1.Platform
121 if component.Platform != nil {
122 platform = &imgspecv1.Platform{
123 Architecture: component.Platform.Architecture,
124 OS: component.Platform.OS,
125 OSVersion: component.Platform.OSVersion,
126 OSFeatures: dupStringSlice(component.Platform.OSFeatures),
127 Variant: component.Platform.Variant,
128 }
129 }
130 m := imgspecv1.Descriptor{
131 MediaType: component.MediaType,
132 Size: component.Size,
133 Digest: component.Digest,
134 URLs: dupStringSlice(component.URLs),
135 Annotations: dupStringStringMap(component.Annotations),
136 Platform: platform,
137 }
138 index.Manifests[i] = m
139 }
140 return &index
141 }
142
143 // OCI1IndexClone creates a deep copy of the passed-in index.
144 func OCI1IndexClone(index *OCI1Index) *OCI1Index {
145 return OCI1IndexFromComponents(index.Manifests, index.Annotations)
146 }
147
148 // ToOCI1Index returns the index encoded as an OCI1 index.
149 func (index *OCI1Index) ToOCI1Index() (*OCI1Index, error) {
150 return OCI1IndexClone(index), nil
151 }
152
153 // ToSchema2List returns the index encoded as a Schema2 list.
154 func (index *OCI1Index) ToSchema2List() (*Schema2List, error) {
155 components := make([]Schema2ManifestDescriptor, 0, len(index.Manifests))
156 for _, manifest := range index.Manifests {
157 platform := manifest.Platform
158 if platform == nil {
159 platform = &imgspecv1.Platform{
160 OS: runtime.GOOS,
161 Architecture: runtime.GOARCH,
162 }
163 }
164 converted := Schema2ManifestDescriptor{
165 Schema2Descriptor{
166 MediaType: manifest.MediaType,
167 Size: manifest.Size,
168 Digest: manifest.Digest,
169 URLs: dupStringSlice(manifest.URLs),
170 },
171 Schema2PlatformSpec{
172 OS: platform.OS,
173 Architecture: platform.Architecture,
174 OSFeatures: dupStringSlice(platform.OSFeatures),
175 OSVersion: platform.OSVersion,
176 Variant: platform.Variant,
177 },
178 }
179 components = append(components, converted)
180 }
181 s2 := Schema2ListFromComponents(components)
182 return s2, nil
183 }
184
185 // OCI1IndexFromManifest creates an OCI1 manifest index instance from marshalled
186 // JSON, presumably generated by encoding a OCI1 manifest index.
187 func OCI1IndexFromManifest(manifest []byte) (*OCI1Index, error) {
188 index := OCI1Index{
189 Index: imgspecv1.Index{
190 Versioned: imgspec.Versioned{SchemaVersion: 2},
191 Manifests: []imgspecv1.Descriptor{},
192 Annotations: make(map[string]string),
193 },
194 }
195 if err := json.Unmarshal(manifest, &index); err != nil {
196 return nil, errors.Wrapf(err, "error unmarshaling OCI1Index %q", string(manifest))
197 }
198 return &index, nil
199 }
200
201 // Clone returns a deep copy of this list and its contents.
202 func (index *OCI1Index) Clone() List {
203 return OCI1IndexClone(index)
204 }
205
206 // ConvertToMIMEType converts the passed-in image index to a manifest list of
207 // the specified type.
208 func (index *OCI1Index) ConvertToMIMEType(manifestMIMEType string) (List, error) {
209 switch normalized := NormalizedMIMEType(manifestMIMEType); normalized {
210 case DockerV2ListMediaType:
211 return index.ToSchema2List()
212 case imgspecv1.MediaTypeImageIndex:
213 return index.Clone(), nil
214 case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType:
215 return nil, fmt.Errorf("Can not convert image index to MIME type %q, which is not a list type", manifestMIMEType)
216 default:
217 // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
218 return nil, fmt.Errorf("Unimplemented manifest MIME type %s", manifestMIMEType)
219 }
220 }
66
77 "github.com/containers/image/v4/types"
88 "github.com/containers/storage/pkg/archive"
9 digest "github.com/opencontainers/go-digest"
910 "github.com/pkg/errors"
1011 )
1112
104105 return d.unpackedDest.TryReusingBlob(ctx, info, cache, canSubstitute)
105106 }
106107
107 // PutManifest writes manifest to the destination
108 func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte) error {
109 return d.unpackedDest.PutManifest(ctx, m)
108 // PutManifest writes the manifest to the destination.
109 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to overwrite the manifest for (when
110 // the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
111 // It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated
112 // by `manifest.Digest()`.
113 func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error {
114 return d.unpackedDest.PutManifest(ctx, m, instanceDigest)
110115 }
111116
112 func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
113 return d.unpackedDest.PutSignatures(ctx, signatures)
117 // PutSignatures writes a set of signatures to the destination.
118 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
119 // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
120 func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
121 return d.unpackedDest.PutSignatures(ctx, signatures, instanceDigest)
114122 }
115123
116124 // Commit marks the process of storing the image as successful and asks for the image to be persisted
117125 // after the directory is made, it is tarred up into a file and the directory is deleted
118 func (d *ociArchiveImageDestination) Commit(ctx context.Context) error {
119 if err := d.unpackedDest.Commit(ctx); err != nil {
126 func (d *ociArchiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
127 if err := d.unpackedDest.Commit(ctx, unparsedToplevel); err != nil {
120128 return errors.Wrapf(err, "error storing image %q", d.ref.image)
121129 }
122130
9595 return s.unpackedSrc.GetSignatures(ctx, instanceDigest)
9696 }
9797
98 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
99 func (s *ociArchiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
100 return nil, nil
98 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
99 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
100 // to read the image's layers.
101 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
102 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
103 // (e.g. if the source never returns manifest lists).
104 // The Digest field is guaranteed to be provided; Size may be -1.
105 // WARNING: The list may contain duplicates, and they are semantically relevant.
106 func (s *ociArchiveImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
107 return s.unpackedSrc.LayerInfosForCopy(ctx, instanceDigest)
101108 }
3737 Versioned: imgspec.Versioned{
3838 SchemaVersion: 2,
3939 },
40 Annotations: make(map[string]string),
4041 }
4142 }
4243
7273 func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
7374 return []string{
7475 imgspecv1.MediaTypeImageManifest,
76 imgspecv1.MediaTypeImageIndex,
7577 }
7678 }
7779
204206 return true, types.BlobInfo{Digest: info.Digest, Size: finfo.Size()}, nil
205207 }
206208
207 // PutManifest writes manifest to the destination.
209 // PutManifest writes a manifest to the destination. Per our list of supported manifest MIME types,
210 // this should be either an OCI manifest (possibly converted to this format by the caller) or index,
211 // neither of which we'll need to modify further.
212 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to overwrite the manifest for (when
213 // the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
214 // It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated
215 // by `manifest.Digest()`.
208216 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
209217 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
210218 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
211 func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte) error {
212 digest, err := manifest.Digest(m)
213 if err != nil {
214 return err
215 }
219 func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error {
220 var digest digest.Digest
221 var err error
222 if instanceDigest != nil {
223 digest = *instanceDigest
224 } else {
225 digest, err = manifest.Digest(m)
226 if err != nil {
227 return err
228 }
229 }
230
231 blobPath, err := d.ref.blobPath(digest, d.sharedBlobDir)
232 if err != nil {
233 return err
234 }
235 if err := ensureParentDirectoryExists(blobPath); err != nil {
236 return err
237 }
238 if err := ioutil.WriteFile(blobPath, m, 0644); err != nil {
239 return err
240 }
241
242 if instanceDigest != nil {
243 return nil
244 }
245
246 // If we had platform information, we'd build an imgspecv1.Platform structure here.
247
248 // Start filling out the descriptor for this entry
216249 desc := imgspecv1.Descriptor{}
217250 desc.Digest = digest
218 // TODO(runcom): beaware and add support for OCI manifest list
219 desc.MediaType = imgspecv1.MediaTypeImageManifest
220251 desc.Size = int64(len(m))
221
222 blobPath, err := d.ref.blobPath(digest, d.sharedBlobDir)
223 if err != nil {
224 return err
225 }
226 if err := ensureParentDirectoryExists(blobPath); err != nil {
227 return err
228 }
229 if err := ioutil.WriteFile(blobPath, m, 0644); err != nil {
230 return err
231 }
232
233252 if d.ref.image != "" {
234 annotations := make(map[string]string)
235 annotations["org.opencontainers.image.ref.name"] = d.ref.image
236 desc.Annotations = annotations
237 }
238 desc.Platform = &imgspecv1.Platform{
239 Architecture: runtime.GOARCH,
240 OS: runtime.GOOS,
241 }
253 desc.Annotations = make(map[string]string)
254 desc.Annotations[imgspecv1.AnnotationRefName] = d.ref.image
255 }
256
257 // If we knew the MIME type, we wouldn't have to guess here.
258 desc.MediaType = manifest.GuessMIMEType(m)
259
242260 d.addManifest(&desc)
243261
244262 return nil
245263 }
246264
247265 func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
266 // If the new entry has a name, remove any conflicting names which we already have.
267 if desc.Annotations != nil && desc.Annotations[imgspecv1.AnnotationRefName] != "" {
268 // The name is being set on a new entry, so remove any older ones that had the same name.
269 // We might be storing an index and all of its component images, and we'll want to attach
270 // the name to the last one, which is the index.
271 for i, manifest := range d.index.Manifests {
272 if manifest.Annotations[imgspecv1.AnnotationRefName] == desc.Annotations[imgspecv1.AnnotationRefName] {
273 delete(d.index.Manifests[i].Annotations, imgspecv1.AnnotationRefName)
274 break
275 }
276 }
277 }
278 // If it has the same digest as another entry in the index, we already overwrote the file,
279 // so just pick up the other information.
248280 for i, manifest := range d.index.Manifests {
249 if manifest.Annotations["org.opencontainers.image.ref.name"] == desc.Annotations["org.opencontainers.image.ref.name"] {
250 // TODO Should there first be a cleanup based on the descriptor we are going to replace?
281 if manifest.Digest == desc.Digest {
282 // Replace it completely.
251283 d.index.Manifests[i] = *desc
252284 return
253285 }
254286 }
287 // It's a new entry to be added to the index.
255288 d.index.Manifests = append(d.index.Manifests, *desc)
256289 }
257290
258 func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
291 // PutSignatures would add the given signatures to the oci layout (currently not supported).
292 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
293 // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
294 func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
259295 if len(signatures) != 0 {
260296 return errors.Errorf("Pushing signatures for OCI images is not supported")
261297 }
266302 // WARNING: This does not have any transactional semantics:
267303 // - Uploaded data MAY be visible to others before Commit() is called
268304 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
269 func (d *ociImageDestination) Commit(ctx context.Context) error {
305 func (d *ociImageDestination) Commit(context.Context, types.UnparsedImage) error {
270306 if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
271307 return err
272308 }
00 package layout
11
22 import (
3 "bytes"
34 "context"
5 "io/ioutil"
46 "os"
57 "path/filepath"
68 "testing"
79
810 "github.com/containers/image/v4/pkg/blobinfocache/memory"
911 "github.com/containers/image/v4/types"
12 digest "github.com/opencontainers/go-digest"
1013 "github.com/pkg/errors"
1114 "github.com/stretchr/testify/assert"
1215 "github.com/stretchr/testify/require"
5558 _, err = dest.PutBlob(context.Background(), reader, types.BlobInfo{Digest: blobDigest, Size: -1}, cache, false)
5659 assert.Error(t, err)
5760 assert.Contains(t, digestErrorString, err.Error())
58 err = dest.Commit(context.Background())
61 err = dest.Commit(context.Background(), nil)
5962 assert.NoError(t, err)
6063
6164 _, err = os.Lstat(blobPath)
9598 ociRef, ok := ref.(ociReference)
9699 require.True(t, ok)
97100
101 putTestConfig(t, ociRef, tmpDir)
98102 putTestManifest(t, ociRef, tmpDir)
99103 putTestManifest(t, ociRef, tmpDir)
100104
101105 index, err := ociRef.getIndex()
102106 assert.NoError(t, err)
103 assert.Equal(t, 1, len(index.Manifests), "Unexpected number of manifests")
107 assert.Equal(t, 2, len(index.Manifests), "Unexpected number of manifests")
104108 }
105109
106 func putTestManifest(t *testing.T, ociRef ociReference, tmpDir string) {
110 func putTestConfig(t *testing.T, ociRef ociReference, tmpDir string) {
111 data, err := ioutil.ReadFile("../../image/fixtures/oci1-config.json")
112 assert.NoError(t, err)
107113 imageDest, err := newImageDestination(nil, ociRef)
108114 assert.NoError(t, err)
109115
110 data := []byte("abc")
111 err = imageDest.PutManifest(context.Background(), data)
116 cache := memory.New()
117
118 _, err = imageDest.PutBlob(context.Background(), bytes.NewReader(data), types.BlobInfo{Size: int64(len(data)), Digest: digest.FromBytes(data)}, cache, true)
112119 assert.NoError(t, err)
113120
114 err = imageDest.Commit(context.Background())
121 err = imageDest.Commit(context.Background(), nil)
115122 assert.NoError(t, err)
116123
117124 paths := []string{}
120127 return nil
121128 })
122129
123 digest := "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
130 digest := digest.FromBytes(data).Encoded()
131 assert.Contains(t, paths, filepath.Join(tmpDir, "blobs", "sha256", digest), "The OCI directory does not contain the new config data")
132 }
133
134 func putTestManifest(t *testing.T, ociRef ociReference, tmpDir string) {
135 data, err := ioutil.ReadFile("../../image/fixtures/oci1.json")
136 assert.NoError(t, err)
137 imageDest, err := newImageDestination(nil, ociRef)
138 assert.NoError(t, err)
139
140 err = imageDest.PutManifest(context.Background(), data, nil)
141 assert.NoError(t, err)
142
143 err = imageDest.Commit(context.Background(), nil)
144 assert.NoError(t, err)
145
146 paths := []string{}
147 filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error {
148 paths = append(paths, path)
149 return nil
150 })
151
152 digest := digest.FromBytes(data).Encoded()
124153 assert.Contains(t, paths, filepath.Join(tmpDir, "blobs", "sha256", digest), "The OCI directory does not contain the new manifest data")
125154 }
77 "os"
88 "strconv"
99
10 "github.com/containers/image/v4/manifest"
1011 "github.com/containers/image/v4/pkg/tlsclientconfig"
1112 "github.com/containers/image/v4/types"
1213 "github.com/docker/go-connections/tlsconfig"
1718
1819 type ociImageSource struct {
1920 ref ociReference
21 index *imgspecv1.Index
2022 descriptor imgspecv1.Descriptor
2123 client *http.Client
2224 sharedBlobDir string
4042 if err != nil {
4143 return nil, err
4244 }
43 d := &ociImageSource{ref: ref, descriptor: descriptor, client: client}
45 index, err := ref.getIndex()
46 if err != nil {
47 return nil, err
48 }
49 d := &ociImageSource{ref: ref, index: index, descriptor: descriptor, client: client}
4450 if sys != nil {
4551 // TODO(jonboulle): check dir existence?
4652 d.sharedBlobDir = sys.OCISharedBlobDirPath
6571 func (s *ociImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
6672 var dig digest.Digest
6773 var mimeType string
74 var err error
75
6876 if instanceDigest == nil {
6977 dig = digest.Digest(s.descriptor.Digest)
7078 mimeType = s.descriptor.MediaType
7179 } else {
7280 dig = *instanceDigest
73 // XXX: instanceDigest means that we don't immediately have the context of what
74 // mediaType the manifest has. In OCI this means that we don't know
75 // what reference it came from, so we just *assume* that its
76 // MediaTypeImageManifest.
77 // FIXME: We should actually be able to look up the manifest in the index,
78 // and see the MIME type there.
79 mimeType = imgspecv1.MediaTypeImageManifest
81 for _, md := range s.index.Manifests {
82 if md.Digest == dig {
83 mimeType = md.MediaType
84 break
85 }
86 }
8087 }
8188
8289 manifestPath, err := s.ref.blobPath(dig, s.sharedBlobDir)
8390 if err != nil {
8491 return nil, "", err
8592 }
93
8694 m, err := ioutil.ReadFile(manifestPath)
8795 if err != nil {
8896 return nil, "", err
97 }
98 if mimeType == "" {
99 mimeType = manifest.GuessMIMEType(m)
89100 }
90101
91102 return m, mimeType, nil
156167 return nil, 0, errWrap
157168 }
158169
159 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
160 func (s *ociImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
170 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
171 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
172 // to read the image's layers.
173 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
174 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
175 // (e.g. if the source never returns manifest lists).
176 // The Digest field is guaranteed to be provided; Size may be -1.
177 // WARNING: The list may contain duplicates, and they are semantically relevant.
178 func (s *ociImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
161179 return nil, nil
162180 }
163181
194194 } else {
195195 // if image specified, look through all manifests for a match
196196 for _, md := range index.Manifests {
197 if md.MediaType != imgspecv1.MediaTypeImageManifest {
197 if md.MediaType != imgspecv1.MediaTypeImageManifest && md.MediaType != imgspecv1.MediaTypeImageIndex {
198198 continue
199199 }
200 refName, ok := md.Annotations["org.opencontainers.image.ref.name"]
200 refName, ok := md.Annotations[imgspecv1.AnnotationRefName]
201201 if !ok {
202202 continue
203203 }
230230 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
231231 // (e.g. if the source never returns manifest lists).
232232 func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
233 var imageName string
233 var imageStreamImageName string
234234 if instanceDigest == nil {
235235 if err := s.ensureImageIsResolved(ctx); err != nil {
236236 return nil, err
237237 }
238 imageName = s.imageStreamImageName
238 imageStreamImageName = s.imageStreamImageName
239239 } else {
240 imageName = instanceDigest.String()
241 }
242 image, err := s.client.getImage(ctx, imageName)
240 imageStreamImageName = instanceDigest.String()
241 }
242 image, err := s.client.getImage(ctx, imageStreamImageName)
243243 if err != nil {
244244 return nil, err
245245 }
252252 return sigs, nil
253253 }
254254
255 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
256 func (s *openshiftImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
255 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
256 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
257 // to read the image's layers.
258 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
259 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
260 // (e.g. if the source never returns manifest lists).
261 // The Digest field is guaranteed to be provided; Size may be -1.
262 // WARNING: The list may contain duplicates, and they are semantically relevant.
263 func (s *openshiftImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
257264 return nil, nil
258265 }
259266
413420 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
414421 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
415422 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
416 func (d *openshiftImageDestination) PutManifest(ctx context.Context, m []byte) error {
417 manifestDigest, err := manifest.Digest(m)
418 if err != nil {
419 return err
420 }
421 d.imageStreamImageName = manifestDigest.String()
422
423 return d.docker.PutManifest(ctx, m)
424 }
425
426 func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
427 if d.imageStreamImageName == "" {
428 return errors.Errorf("Internal error: Unknown manifest digest, can't add signatures")
429 }
423 func (d *openshiftImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error {
424 if instanceDigest == nil {
425 manifestDigest, err := manifest.Digest(m)
426 if err != nil {
427 return err
428 }
429 d.imageStreamImageName = manifestDigest.String()
430 }
431 return d.docker.PutManifest(ctx, m, instanceDigest)
432 }
433
434 func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
435 var imageStreamName string
436 if instanceDigest == nil {
437 if d.imageStreamImageName == "" {
438 return errors.Errorf("Internal error: Unknown manifest digest, can't add signatures")
439 }
440 imageStreamName = d.imageStreamImageName
441 } else {
442 imageStreamName = instanceDigest.String()
443 }
444
430445 // Because image signatures are a shared resource in Atomic Registry, the default upload
431446 // always adds signatures. Eventually we should also allow removing signatures.
432447
434449 return nil // No need to even read the old state.
435450 }
436451
437 image, err := d.client.getImage(ctx, d.imageStreamImageName)
452 image, err := d.client.getImage(ctx, imageStreamName)
438453 if err != nil {
439454 return err
440455 }
459474 if err != nil || n != 16 {
460475 return errors.Wrapf(err, "Error generating random signature len %d", n)
461476 }
462 signatureName = fmt.Sprintf("%s@%032x", d.imageStreamImageName, randBytes)
477 signatureName = fmt.Sprintf("%s@%032x", imageStreamName, randBytes)
463478 if _, ok := existingSigNames[signatureName]; !ok {
464479 break
465480 }
488503 // WARNING: This does not have any transactional semantics:
489504 // - Uploaded data MAY be visible to others before Commit() is called
490505 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
491 func (d *openshiftImageDestination) Commit(ctx context.Context) error {
492 return d.docker.Commit(ctx)
506 func (d *openshiftImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
507 return d.docker.Commit(ctx, unparsedToplevel)
493508 }
494509
495510 // These structs are subsets of github.com/openshift/origin/pkg/image/api/v1 and its dependencies.
375375 }
376376
377377 // PutManifest writes manifest to the destination.
378 // The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
379 // there can be no secondary manifests.
378380 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
379381 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
380382 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
381 func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error {
383 func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error {
384 if instanceDigest != nil {
385 return errors.New(`Manifest lists are not supported by "ostree:"`)
386 }
387
382388 d.manifest = string(manifestBlob)
383389
384390 if err := json.Unmarshal(manifestBlob, &d.schema); err != nil {
399405 return ioutil.WriteFile(manifestPath, manifestBlob, 0644)
400406 }
401407
402 func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
408 // PutSignatures writes signatures to the destination.
409 // The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so
410 // there can be no secondary manifests.
411 func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
412 if instanceDigest != nil {
413 return errors.New(`Manifest lists are not supported by "ostree:"`)
414 }
415
403416 path := filepath.Join(d.tmpDirPath, d.ref.signaturePath(0))
404417 if err := ensureParentDirectoryExists(path); err != nil {
405418 return err
415428 return nil
416429 }
417430
418 func (d *ostreeImageDestination) Commit(ctx context.Context) error {
431 func (d *ostreeImageDestination) Commit(context.Context, types.UnparsedImage) error {
419432 runtime.LockOSThread()
420433 defer runtime.UnlockOSThread()
421434
9797
9898 // GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
9999 // It may use a remote (= slow) service.
100 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
101 // as the primary manifest can not be a list, so there can be non-default instances.
100102 func (s *ostreeImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
101103 if instanceDigest != nil {
102 return nil, "", errors.Errorf(`Manifest lists are not supported by "ostree:"`)
104 return nil, "", errors.New(`Manifest lists are not supported by "ostree:"`)
103105 }
104106 if s.repo == nil {
105107 repo, err := openRepo(s.ref.repo)
274276
275277 // Ensure s.compressed is initialized. It is build by LayerInfosForCopy.
276278 if s.compressed == nil {
277 _, err := s.LayerInfosForCopy(ctx)
279 _, err := s.LayerInfosForCopy(ctx, nil)
278280 if err != nil {
279281 return nil, -1, err
280282 }
336338 return rc, layerSize, nil
337339 }
338340
341 // GetSignatures returns the image's signatures. It may use a remote (= slow) service.
342 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
343 // as there can be no secondary manifests.
339344 func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
340345 if instanceDigest != nil {
341 return nil, errors.New("manifest lists are not supported by this transport")
346 return nil, errors.New(`Manifest lists are not supported by "ostree:"`)
342347 }
343348 lenSignatures, err := s.getLenSignatures()
344349 if err != nil {
371376 return signatures, nil
372377 }
373378
374 // LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of
375 // the image, after they've been decompressed.
376 func (s *ostreeImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
379 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
380 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
381 // to read the image's layers.
382 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
383 // as the primary manifest can not be a list, so there can be secondary manifests.
384 // The Digest field is guaranteed to be provided; Size may be -1.
385 // WARNING: The list may contain duplicates, and they are semantically relevant.
386 func (s *ostreeImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
387 if instanceDigest != nil {
388 return nil, errors.New(`Manifest lists are not supported by "ostree:"`)
389 }
390
377391 updatedBlobInfos := []types.BlobInfo{}
378392 manifestBlob, manifestType, err := s.GetManifest(ctx, nil)
379393 if err != nil {
55 "bytes"
66 "context"
77 "encoding/json"
8 stderrors "errors"
89 "fmt"
910 "io"
1011 "io/ioutil"
3132 var (
3233 // ErrBlobDigestMismatch is returned when PutBlob() is given a blob
3334 // with a digest-based name that doesn't match its contents.
34 ErrBlobDigestMismatch = errors.New("blob digest mismatch")
35 ErrBlobDigestMismatch = stderrors.New("blob digest mismatch")
3536 // ErrBlobSizeMismatch is returned when PutBlob() is given a blob
3637 // with an expected size that doesn't match the reader.
37 ErrBlobSizeMismatch = errors.New("blob size mismatch")
38 // ErrNoManifestLists is returned when GetManifest() is called.
39 // with a non-nil instanceDigest.
40 ErrNoManifestLists = errors.New("manifest lists are not supported by this transport")
38 ErrBlobSizeMismatch = stderrors.New("blob size mismatch")
4139 // ErrNoSuchImage is returned when we attempt to access an image which
4240 // doesn't exist in the storage area.
4341 ErrNoSuchImage = storage.ErrNotAnImage
4442 )
4543
4644 type storageImageSource struct {
47 imageRef storageReference
48 image *storage.Image
49 layerPosition map[digest.Digest]int // Where we are in reading a blob's layers
50 cachedManifest []byte // A cached copy of the manifest, if already known, or nil
51 getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions
52 SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
45 imageRef storageReference
46 image *storage.Image
47 layerPosition map[digest.Digest]int // Where we are in reading a blob's layers
48 cachedManifest []byte // A cached copy of the manifest, if already known, or nil
49 getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions
50 SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
51 SignaturesSizes map[digest.Digest][]int `json:"signatures-sizes,omitempty"` // List of sizes of each signature slice
5352 }
5453
5554 type storageImageDestination struct {
56 imageRef storageReference
57 directory string // Temporary directory where we store blobs until Commit() time
58 nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs
59 manifest []byte // Manifest contents, temporary
60 signatures []byte // Signature contents, temporary
61 putBlobMutex sync.Mutex // Mutex to sync state for parallel PutBlob executions
62 blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs
63 fileSizes map[digest.Digest]int64 // Mapping from layer blobsums to their sizes
64 filenames map[digest.Digest]string // Mapping from layer blobsums to names of files we used to hold them
65 SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
55 imageRef storageReference
56 directory string // Temporary directory where we store blobs until Commit() time
57 nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs
58 manifest []byte // Manifest contents, temporary
59 signatures []byte // Signature contents, temporary
60 signatureses map[digest.Digest][]byte // Instance signature contents, temporary
61 putBlobMutex sync.Mutex // Mutex to sync state for parallel PutBlob executions
62 blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs
63 fileSizes map[digest.Digest]int64 // Mapping from layer blobsums to their sizes
64 filenames map[digest.Digest]string // Mapping from layer blobsums to names of files we used to hold them
65 SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
66 SignaturesSizes map[digest.Digest][]int `json:"signatures-sizes,omitempty"` // Sizes of each manifest's signature slice
6667 }
6768
6869 type storageImageCloser struct {
7172 }
7273
7374 // manifestBigDataKey returns a key suitable for recording a manifest with the specified digest using storage.Store.ImageBigData and related functions.
74 // If a specific manifest digest is explicitly requested by the user, the key retruned function should be used preferably;
75 // If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably;
7576 // for compatibility, if a manifest is not available under this key, check also storage.ImageDigestBigDataKey
7677 func manifestBigDataKey(digest digest.Digest) string {
7778 return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String()
7879 }
7980
81 // signatureBigDataKey returns a key suitable for recording the signatures associated with the manifest with the specified digest using storage.Store.ImageBigData and related functions.
82 // If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably;
83 func signatureBigDataKey(digest digest.Digest) string {
84 return "signature-" + digest.Encoded()
85 }
86
8087 // newImageSource sets up an image for reading.
81 func newImageSource(imageRef storageReference) (*storageImageSource, error) {
88 func newImageSource(ctx context.Context, sys *types.SystemContext, imageRef storageReference) (*storageImageSource, error) {
8289 // First, locate the image.
83 img, err := imageRef.resolveImage()
90 img, err := imageRef.resolveImage(sys)
8491 if err != nil {
8592 return nil, err
8693 }
8794
8895 // Build the reader object.
8996 image := &storageImageSource{
90 imageRef: imageRef,
91 image: img,
92 layerPosition: make(map[digest.Digest]int),
93 SignatureSizes: []int{},
97 imageRef: imageRef,
98 image: img,
99 layerPosition: make(map[digest.Digest]int),
100 SignatureSizes: []int{},
101 SignaturesSizes: make(map[digest.Digest][]int),
94102 }
95103 if img.Metadata != "" {
96104 if err := json.Unmarshal([]byte(img.Metadata), image); err != nil {
181189 // GetManifest() reads the image's manifest.
182190 func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) (manifestBlob []byte, MIMEType string, err error) {
183191 if instanceDigest != nil {
184 return nil, "", ErrNoManifestLists
192 key := manifestBigDataKey(*instanceDigest)
193 blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key)
194 if err != nil {
195 return nil, "", errors.Wrapf(err, "error reading manifest for image instance %q", *instanceDigest)
196 }
197 return blob, manifest.GuessMIMEType(blob), err
185198 }
186199 if len(s.cachedManifest) == 0 {
187200 // The manifest is stored as a big data item.
213226
214227 // LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of
215228 // the image, after they've been decompressed.
216 func (s *storageImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
217 manifestBlob, manifestType, err := s.GetManifest(ctx, nil)
229 func (s *storageImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
230 manifestBlob, manifestType, err := s.GetManifest(ctx, instanceDigest)
218231 if err != nil {
219232 return nil, errors.Wrapf(err, "error reading image manifest for %q", s.image.ID)
233 }
234 if manifest.MIMETypeIsMultiImage(manifestType) {
235 return nil, errors.Errorf("can't copy layers for a manifest list (shouldn't be attempted)")
220236 }
221237 man, err := manifest.FromBlob(manifestBlob, manifestType)
222238 if err != nil {
291307
292308 // GetSignatures() parses the image's signatures blob into a slice of byte slices.
293309 func (s *storageImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) (signatures [][]byte, err error) {
294 if instanceDigest != nil {
295 return nil, ErrNoManifestLists
296 }
297310 var offset int
298311 sigslice := [][]byte{}
299312 signature := []byte{}
300 if len(s.SignatureSizes) > 0 {
301 signatureBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, "signatures")
313 signatureSizes := s.SignatureSizes
314 key := "signatures"
315 instance := "default instance"
316 if instanceDigest != nil {
317 signatureSizes = s.SignaturesSizes[*instanceDigest]
318 key = signatureBigDataKey(*instanceDigest)
319 instance = instanceDigest.Encoded()
320 }
321 if len(signatureSizes) > 0 {
322 signatureBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key)
302323 if err != nil {
303 return nil, errors.Wrapf(err, "error looking up signatures data for image %q", s.image.ID)
324 return nil, errors.Wrapf(err, "error looking up signatures data for image %q (%s)", s.image.ID, instance)
304325 }
305326 signature = signatureBlob
306327 }
307 for _, length := range s.SignatureSizes {
328 for _, length := range signatureSizes {
329 if offset+length > len(signature) {
330 return nil, errors.Wrapf(err, "error looking up signatures data for image %q (%s): expected at least %d bytes, only found %d", s.image.ID, instance, len(signature), offset+length)
331 }
308332 sigslice = append(sigslice, signature[offset:offset+length])
309333 offset += length
310334 }
311335 if offset != len(signature) {
312 return nil, errors.Errorf("signatures data contained %d extra bytes", len(signatures)-offset)
336 return nil, errors.Errorf("signatures data (%s) contained %d extra bytes", instance, len(signatures)-offset)
313337 }
314338 return sigslice, nil
315339 }
322346 return nil, errors.Wrapf(err, "error creating a temporary directory")
323347 }
324348 image := &storageImageDestination{
325 imageRef: imageRef,
326 directory: directory,
327 blobDiffIDs: make(map[digest.Digest]digest.Digest),
328 fileSizes: make(map[digest.Digest]int64),
329 filenames: make(map[digest.Digest]string),
330 SignatureSizes: []int{},
349 imageRef: imageRef,
350 directory: directory,
351 signatureses: make(map[digest.Digest][]byte),
352 blobDiffIDs: make(map[digest.Digest]digest.Digest),
353 fileSizes: make(map[digest.Digest]int64),
354 filenames: make(map[digest.Digest]string),
355 SignatureSizes: []int{},
356 SignaturesSizes: make(map[digest.Digest][]int),
331357 }
332358 return image, nil
333359 }
403429 }
404430 // Ensure that any information that we were given about the blob is correct.
405431 if blobinfo.Digest.Validate() == nil && blobinfo.Digest != hasher.Digest() {
406 return errorBlobInfo, ErrBlobDigestMismatch
432 return errorBlobInfo, errors.WithStack(ErrBlobDigestMismatch)
407433 }
408434 if blobinfo.Size >= 0 && blobinfo.Size != counter.Count {
409 return errorBlobInfo, ErrBlobSizeMismatch
435 return errorBlobInfo, errors.WithStack(ErrBlobSizeMismatch)
410436 }
411437 // Record information about the blob.
412438 s.putBlobMutex.Lock()
578604 return nil, errors.New("blob not found")
579605 }
580606
581 func (s *storageImageDestination) Commit(ctx context.Context) error {
607 func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
608 if len(s.manifest) == 0 {
609 return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()")
610 }
611 toplevelManifest, _, err := unparsedToplevel.Manifest(ctx)
612 if err != nil {
613 return errors.Wrapf(err, "error retrieving top-level manifest")
614 }
615 // If the name we're saving to includes a digest, then check that the
616 // manifests that we're about to save all either match the one from the
617 // unparsedToplevel, or match the digest in the name that we're using.
618 if s.imageRef.named != nil {
619 if digested, ok := s.imageRef.named.(reference.Digested); ok {
620 matches, err := manifest.MatchesDigest(s.manifest, digested.Digest())
621 if err != nil {
622 return err
623 }
624 if !matches {
625 matches, err = manifest.MatchesDigest(toplevelManifest, digested.Digest())
626 if err != nil {
627 return err
628 }
629 }
630 if !matches {
631 return fmt.Errorf("Manifest to be saved does not match expected digest %s", digested.Digest())
632 }
633 }
634 }
582635 // Find the list of layer blobs.
583636 if len(s.manifest) == 0 {
584637 return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()")
746799 return errors.Wrapf(err, "error saving big data %q for image %q", blob.String(), img.ID)
747800 }
748801 }
749 // Set the reference's name on the image.
802 // Set the reference's name on the image. We don't need to worry about avoiding duplicate
803 // values because SetNames() will deduplicate the list that we pass to it.
750804 if name := s.imageRef.DockerReference(); len(oldNames) > 0 || name != nil {
751805 names := []string{}
752806 if name != nil {
764818 }
765819 logrus.Debugf("set names of image %q to %v", img.ID, names)
766820 }
767 // Save the manifest. Allow looking it up by digest by using the key convention defined by the Store.
821 // Save the unparsedToplevel's manifest.
822 if len(toplevelManifest) != 0 {
823 manifestDigest, err := manifest.Digest(toplevelManifest)
824 if err != nil {
825 return errors.Wrapf(err, "error digesting top-level manifest")
826 }
827 key := manifestBigDataKey(manifestDigest)
828 if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, toplevelManifest, manifest.Digest); err != nil {
829 if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
830 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
831 }
832 logrus.Debugf("error saving top-level manifest for image %q: %v", img.ID, err)
833 return errors.Wrapf(err, "error saving top-level manifest for image %q", img.ID)
834 }
835 }
836 // Save the image's manifest. Allow looking it up by digest by using the key convention defined by the Store.
768837 // Record the manifest twice: using a digest-specific key to allow references to that specific digest instance,
769838 // and using storage.ImageDigestBigDataKey for future users that don’t specify any digest and for compatibility with older readers.
770839 manifestDigest, err := manifest.Digest(s.manifest)
771840 if err != nil {
772841 return errors.Wrapf(err, "error computing manifest digest")
773842 }
774 if err := s.imageRef.transport.store.SetImageBigData(img.ID, manifestBigDataKey(manifestDigest), s.manifest, manifest.Digest); err != nil {
843 key := manifestBigDataKey(manifestDigest)
844 if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, s.manifest, manifest.Digest); err != nil {
775845 if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
776846 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
777847 }
778848 logrus.Debugf("error saving manifest for image %q: %v", img.ID, err)
779 return err
780 }
781 if err := s.imageRef.transport.store.SetImageBigData(img.ID, storage.ImageDigestBigDataKey, s.manifest, manifest.Digest); err != nil {
849 return errors.Wrapf(err, "error saving manifest for image %q", img.ID)
850 }
851 key = storage.ImageDigestBigDataKey
852 if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, s.manifest, manifest.Digest); err != nil {
782853 if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
783854 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
784855 }
785856 logrus.Debugf("error saving manifest for image %q: %v", img.ID, err)
786 return err
857 return errors.Wrapf(err, "error saving manifest for image %q", img.ID)
787858 }
788859 // Save the signatures, if we have any.
789860 if len(s.signatures) > 0 {
792863 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
793864 }
794865 logrus.Debugf("error saving signatures for image %q: %v", img.ID, err)
795 return err
866 return errors.Wrapf(err, "error saving signatures for image %q", img.ID)
867 }
868 }
869 for instanceDigest, signatures := range s.signatureses {
870 key := signatureBigDataKey(instanceDigest)
871 if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, signatures, manifest.Digest); err != nil {
872 if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
873 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
874 }
875 logrus.Debugf("error saving signatures for image %q: %v", img.ID, err)
876 return errors.Wrapf(err, "error saving signatures for image %q", img.ID)
796877 }
797878 }
798879 // Save our metadata.
802883 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
803884 }
804885 logrus.Debugf("error encoding metadata for image %q: %v", img.ID, err)
805 return err
886 return errors.Wrapf(err, "error encoding metadata for image %q", img.ID)
806887 }
807888 if len(metadata) != 0 {
808889 if err = s.imageRef.transport.store.SetMetadata(img.ID, string(metadata)); err != nil {
810891 logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
811892 }
812893 logrus.Debugf("error saving metadata for image %q: %v", img.ID, err)
813 return err
894 return errors.Wrapf(err, "error saving metadata for image %q", img.ID)
814895 }
815896 logrus.Debugf("saved image metadata %q", string(metadata))
816897 }
829910 }
830911
831912 // PutManifest writes the manifest to the destination.
832 func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error {
833 if s.imageRef.named != nil {
834 if digested, ok := s.imageRef.named.(reference.Digested); ok {
835 matches, err := manifest.MatchesDigest(manifestBlob, digested.Digest())
836 if err != nil {
837 return err
838 }
839 if !matches {
840 return fmt.Errorf("Manifest does not match expected digest %s", digested.Digest())
841 }
842 }
843 }
844
845 s.manifest = make([]byte, len(manifestBlob))
846 copy(s.manifest, manifestBlob)
913 func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error {
914 newBlob := make([]byte, len(manifestBlob))
915 copy(newBlob, manifestBlob)
916 s.manifest = newBlob
847917 return nil
848918 }
849919
872942 }
873943
874944 // PutSignatures records the image's signatures for committing as a single data blob.
875 func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
945 func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error {
876946 sizes := []int{}
877947 sigblob := []byte{}
878948 for _, sig := range signatures {
882952 copy(newblob[len(sigblob):], sig)
883953 sigblob = newblob
884954 }
885 s.signatures = sigblob
886 s.SignatureSizes = sizes
955 if instanceDigest == nil {
956 s.signatures = sigblob
957 s.SignatureSizes = sizes
958 }
959 if instanceDigest == nil && len(s.manifest) > 0 {
960 manifestDigest, err := manifest.Digest(s.manifest)
961 if err != nil {
962 return err
963 }
964 instanceDigest = &manifestDigest
965 }
966 if instanceDigest != nil {
967 s.signatureses[*instanceDigest] = sigblob
968 s.SignaturesSizes[*instanceDigest] = sizes
969 }
887970 return nil
888971 }
889972
9391022
9401023 // newImage creates an image that also knows its size
9411024 func newImage(ctx context.Context, sys *types.SystemContext, s storageReference) (types.ImageCloser, error) {
942 src, err := newImageSource(s)
1025 src, err := newImageSource(ctx, sys, s)
9431026 if err != nil {
9441027 return nil, err
9451028 }
66 "strings"
77
88 "github.com/containers/image/v4/docker/reference"
9 "github.com/containers/image/v4/manifest"
910 "github.com/containers/image/v4/types"
1011 "github.com/containers/storage"
12 digest "github.com/opencontainers/go-digest"
13 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
1114 "github.com/pkg/errors"
1215 "github.com/sirupsen/logrus"
1316 )
5053 return false
5154 }
5255
56 // imageMatchesSystemContext checks if the passed-in image both contains a
57 // manifest that matches the passed-in digest, and identifies itself as being
58 // appropriate for running on the system that matches sys.
59 // If we somehow ended up sharing the same storage among multiple types of
60 // systems, and managed to download multiple images from the same manifest
61 // list, their image records will all contain copies of the manifest list, and
62 // this check will help us decide which of them we want to return when we've
63 // been asked to resolve an image reference that uses the list's digest to a
64 // specific image ID.
65 func imageMatchesSystemContext(store storage.Store, img *storage.Image, manifestDigest digest.Digest, sys *types.SystemContext) bool {
66 // First, check if the image record has a manifest that matches the
67 // specified digest.
68 key := manifestBigDataKey(manifestDigest)
69 manifestBytes, err := store.ImageBigData(img.ID, key)
70 if err != nil {
71 return false
72 }
73 // The manifest is either a list, or not a list. If it's a list, find
74 // the digest of the instance that matches the current system, and try
75 // to load that manifest from the image record, and use it.
76 manifestType := manifest.GuessMIMEType(manifestBytes)
77 if manifest.MIMETypeIsMultiImage(manifestType) {
78 list, err := manifest.ListFromBlob(manifestBytes, manifestType)
79 if err != nil {
80 return false
81 }
82 manifestDigest, err = list.ChooseInstance(sys)
83 if err != nil {
84 return false
85 }
86 key = manifestBigDataKey(manifestDigest)
87 manifestBytes, err = store.ImageBigData(img.ID, key)
88 if err != nil {
89 return false
90 }
91 manifestType = manifest.GuessMIMEType(manifestBytes)
92 }
93 // Load the image's configuration blob.
94 m, err := manifest.FromBlob(manifestBytes, manifestType)
95 getConfig := func(blobInfo types.BlobInfo) ([]byte, error) {
96 return store.ImageBigData(img.ID, blobInfo.Digest.String())
97 }
98 ii, err := m.Inspect(getConfig)
99 if err != nil {
100 return false
101 }
102 // Build a dummy index containing one instance and information about
103 // the image's target system from the image's configuration.
104 index := manifest.OCI1IndexFromComponents([]imgspecv1.Descriptor{{
105 MediaType: imgspecv1.MediaTypeImageManifest,
106 Digest: manifestDigest,
107 Size: int64(len(manifestBytes)),
108 Platform: &imgspecv1.Platform{
109 OS: ii.Os,
110 Architecture: ii.Architecture,
111 },
112 }}, nil)
113 // Check that ChooseInstance() would select this image for this system,
114 // from a list of images.
115 instanceDigest, err := index.ChooseInstance(sys)
116 if err != nil {
117 return false
118 }
119 // Double-check that we can read the runnable image's manifest from the
120 // image record.
121 key = manifestBigDataKey(instanceDigest)
122 _, err = store.ImageBigData(img.ID, key)
123 return err == nil
124 }
125
53126 // Resolve the reference's name to an image ID in the store, if there's already
54127 // one present with the same name or ID, and return the image.
55 func (s *storageReference) resolveImage() (*storage.Image, error) {
128 func (s *storageReference) resolveImage(sys *types.SystemContext) (*storage.Image, error) {
56129 var loadedImage *storage.Image
57130 if s.id == "" && s.named != nil {
58131 // Look for an image that has the expanded reference name as an explicit Name value.
71144 if err == nil && len(images) > 0 {
72145 for _, image := range images {
73146 if imageMatchesRepo(image, s.named) {
74 loadedImage = image
75 s.id = image.ID
76 break
147 if loadedImage == nil || imageMatchesSystemContext(s.transport.store, image, digested.Digest(), sys) {
148 loadedImage = image
149 s.id = image.ID
150 }
77151 }
78152 }
79153 }
201275 }
202276
203277 func (s storageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
204 img, err := s.resolveImage()
278 img, err := s.resolveImage(sys)
205279 if err != nil {
206280 return err
207281 }
216290 }
217291
218292 func (s storageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
219 return newImageSource(s)
293 return newImageSource(ctx, sys, s)
220294 }
221295
222296 func (s storageReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
1313 "io/ioutil"
1414 "os"
1515 "path/filepath"
16 "reflect"
1617 "strings"
1718 "testing"
1819 "time"
1920
21 imanifest "github.com/containers/image/v4/manifest"
2022 "github.com/containers/image/v4/pkg/blobinfocache/memory"
2123 "github.com/containers/image/v4/types"
2224 "github.com/containers/storage"
269271 }
270272 if n != len(buf) {
271273 t.Fatalf("Short write writing tar header: %d < %d", n, len(buf))
274 }
275 err = twriter.Flush()
276 if err != nil {
277 t.Fatalf("Error flushing output to tar archive: %v", err)
272278 }
273279 }()
274280 _, err = io.Copy(&tbuffer, preader)
393399 manifest = strings.Replace(manifest, "%li", li, -1)
394400 manifest = strings.Replace(manifest, "%ci", sum.Hex(), -1)
395401 t.Logf("this manifest is %q", manifest)
396 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
402 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
397403 t.Fatalf("Error saving manifest to destination: %v", err)
398404 }
399 if err := dest.PutSignatures(context.Background(), signatures); err != nil {
405 if err := dest.PutSignatures(context.Background(), signatures, nil); err != nil {
400406 t.Fatalf("Error saving signatures to destination: %v", err)
401407 }
402 if err := dest.Commit(context.Background()); err != nil {
408 unparsedToplevel := unparsedImage{
409 imageReference: nil,
410 manifestBytes: []byte(manifest),
411 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
412 signatures: signatures,
413 }
414 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
403415 t.Fatalf("Error committing changes to destination: %v", err)
404416 }
405417 dest.Close()
453465 t.Fatalf("GetManifest(%q) returned error %v", ref.StringWithinTransport(), err)
454466 }
455467 t.Logf("this manifest's type appears to be %q", manifestType)
456 sum = ddigest.SHA256.FromBytes([]byte(manifest))
457 _, _, err = src.GetManifest(context.Background(), &sum)
458 if err == nil {
459 t.Fatalf("GetManifest(%q) with an instanceDigest is supposed to fail", ref.StringWithinTransport())
468 sum, err = imanifest.Digest([]byte(manifest))
469 if err != nil {
470 t.Fatalf("manifest.Digest() returned error %v", err)
471 }
472 retrieved, _, err := src.GetManifest(context.Background(), &sum)
473 if err != nil {
474 t.Fatalf("GetManifest(%q) with an instanceDigest is supposed to succeed", ref.StringWithinTransport())
475 }
476 if string(retrieved) != string(manifest) {
477 t.Fatalf("GetManifest(%q) with an instanceDigest retrieved a different manifest", ref.StringWithinTransport())
460478 }
461479 sigs, err := src.GetSignatures(context.Background(), nil)
462480 if err != nil {
473491 t.Fatalf("Signature %d was corrupted", i)
474492 }
475493 }
476 _, err = src.GetSignatures(context.Background(), &sum)
477 if err == nil {
478 t.Fatalf("GetSignatures(%q) with instanceDigest is supposed to fail", ref.StringWithinTransport())
494 sigs2, err := src.GetSignatures(context.Background(), &sum)
495 if err != nil {
496 t.Fatalf("GetSignatures(%q) with instance %s returned error %v", ref.StringWithinTransport(), sum.String(), err)
497 }
498 if !reflect.DeepEqual(sigs, sigs2) {
499 t.Fatalf("GetSignatures(%q) with instance %s returned a different result", ref.StringWithinTransport(), sum.String())
479500 }
480501 for _, layerInfo := range layerInfos {
481502 buf := bytes.Buffer{}
492513 t.Fatalf("Error decompressing layer %q from %q", layerInfo.Digest, ref.StringWithinTransport())
493514 }
494515 n, err := io.Copy(&buf, decompressed)
516 layer.Close()
495517 if layerInfo.Size >= 0 && compressed.Count != layerInfo.Size {
496518 t.Fatalf("Blob size is different than expected: %d != %d, read %d", compressed.Count, layerInfo.Size, n)
497519 }
555577 ]
556578 }
557579 `, digest, size)
558 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
580 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
559581 t.Fatalf("Error storing manifest to destination: %v", err)
560582 }
561 if err := dest.Commit(context.Background()); err != nil {
583 unparsedToplevel := unparsedImage{
584 imageReference: nil,
585 manifestBytes: []byte(manifest),
586 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
587 signatures: nil,
588 }
589 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
562590 t.Fatalf("Error committing changes to destination, first pass: %v", err)
563591 }
564592 dest.Close()
590618 ]
591619 }
592620 `, digest, size)
593 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
621 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
594622 t.Fatalf("Error storing manifest to destination: %v", err)
595623 }
596 if err := dest.Commit(context.Background()); err != nil {
624 unparsedToplevel = unparsedImage{
625 imageReference: nil,
626 manifestBytes: []byte(manifest),
627 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
628 signatures: nil,
629 }
630 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
597631 t.Fatalf("Error committing changes to destination, second pass: %v", err)
598632 }
599633 dest.Close()
642676 ]
643677 }
644678 `, digest, size)
645 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
679 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
646680 t.Fatalf("Error storing manifest to destination: %v", err)
647681 }
648 if err := dest.Commit(context.Background()); err != nil {
682 unparsedToplevel := unparsedImage{
683 imageReference: nil,
684 manifestBytes: []byte(manifest),
685 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
686 signatures: nil,
687 }
688 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
649689 t.Fatalf("Error committing changes to destination, first pass: %v", err)
650690 }
651691 dest.Close()
677717 ]
678718 }
679719 `, digest, size)
680 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
720 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
681721 t.Fatalf("Error storing manifest to destination: %v", err)
682722 }
683 if err := dest.Commit(context.Background()); errors.Cause(err) != storage.ErrDuplicateID {
723 unparsedToplevel = unparsedImage{
724 imageReference: nil,
725 manifestBytes: []byte(manifest),
726 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
727 signatures: nil,
728 }
729 if err := dest.Commit(context.Background(), &unparsedToplevel); errors.Cause(err) != storage.ErrDuplicateID {
684730 if err != nil {
685731 t.Fatalf("Wrong error committing changes to destination, second pass: %v", err)
686732 }
732778 ]
733779 }
734780 `, digest, size)
735 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
781 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
736782 t.Fatalf("Error storing manifest to destination: %v", err)
737783 }
738 if err := dest.Commit(context.Background()); err != nil {
784 unparsedToplevel := unparsedImage{
785 imageReference: nil,
786 manifestBytes: []byte(manifest),
787 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
788 signatures: nil,
789 }
790 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
739791 t.Fatalf("Error committing changes to destination, first pass: %v", err)
740792 }
741793 dest.Close()
767819 ]
768820 }
769821 `, digest, size)
770 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
822 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
771823 t.Fatalf("Error storing manifest to destination: %v", err)
772824 }
773 if err := dest.Commit(context.Background()); errors.Cause(err) != storage.ErrDuplicateID {
825 unparsedToplevel = unparsedImage{
826 imageReference: nil,
827 manifestBytes: []byte(manifest),
828 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
829 signatures: nil,
830 }
831 if err := dest.Commit(context.Background(), &unparsedToplevel); errors.Cause(err) != storage.ErrDuplicateID {
774832 if err != nil {
775833 t.Fatalf("Wrong error committing changes to destination, second pass: %v", err)
776834 }
888946 ]
889947 }
890948 `, configInfo.Size, configInfo.Digest, digest1, size1, digest2, size2)
891 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
949 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
892950 t.Fatalf("Error storing manifest to destination: %v", err)
893951 }
894 if err := dest.Commit(context.Background()); err != nil {
952 unparsedToplevel := unparsedImage{
953 imageReference: nil,
954 manifestBytes: []byte(manifest),
955 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
956 signatures: nil,
957 }
958 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
895959 t.Fatalf("Error committing changes to destination: %v", err)
896960 }
897961 dest.Close()
904968 if usize == -1 || err != nil {
905969 t.Fatalf("Error calculating image size: %v", err)
906970 }
907 if int(usize) != len(config)+int(usize1)+int(usize2)+len(manifest) {
908 t.Fatalf("Unexpected image size: %d != %d + %d + %d + %d", usize, len(config), usize1, usize2, len(manifest))
971 if int(usize) != len(config)+int(usize1)+int(usize2)+2*len(manifest) {
972 t.Fatalf("Unexpected image size: %d != %d + %d + %d + %d (%d)", usize, len(config), usize1, usize2, len(manifest), len(config)+int(usize1)+int(usize2)+2*len(manifest))
909973 }
910974 img.Close()
911975 }
9991063 ]
10001064 }
10011065 `, configInfo.Size, configInfo.Digest, digest1, size1, digest2, size2, digest1, size1, digest2, size2)
1002 if err := dest.PutManifest(context.Background(), []byte(manifest)); err != nil {
1066 if err := dest.PutManifest(context.Background(), []byte(manifest), nil); err != nil {
10031067 t.Fatalf("Error storing manifest to destination: %v", err)
10041068 }
1005 if err := dest.Commit(context.Background()); err != nil {
1069 unparsedToplevel := unparsedImage{
1070 imageReference: nil,
1071 manifestBytes: []byte(manifest),
1072 manifestType: imanifest.GuessMIMEType([]byte(manifest)),
1073 signatures: nil,
1074 }
1075 if err := dest.Commit(context.Background(), &unparsedToplevel); err != nil {
10061076 t.Fatalf("Error committing changes to destination: %v", err)
10071077 }
10081078 dest.Close()
10461116 src.Close()
10471117 img.Close()
10481118 }
1119
1120 type unparsedImage struct {
1121 imageReference types.ImageReference
1122 manifestBytes []byte
1123 manifestType string
1124 signatures [][]byte
1125 }
1126
1127 func (u *unparsedImage) Reference() types.ImageReference {
1128 return u.imageReference
1129 }
1130 func (u *unparsedImage) Manifest(context.Context) ([]byte, string, error) {
1131 return u.manifestBytes, u.manifestType, nil
1132 }
1133 func (u *unparsedImage) Signatures(context.Context) ([][]byte, error) {
1134 return u.signatures, nil
1135 }
287287 }
288288 if sref, ok := ref.(*storageReference); ok {
289289 tmpRef := *sref
290 if img, err := tmpRef.resolveImage(); err == nil {
290 if img, err := tmpRef.resolveImage(&types.SystemContext{}); err == nil {
291291 return img, nil
292292 }
293293 }
247247 }
248248
249249 // GetSignatures returns the image's signatures. It may use a remote (= slow) service.
250 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
251 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
252 // (e.g. if the source never returns manifest lists).
250 // This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil,
251 // as there can be no secondary manifests.
253252 func (*tarballImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
254253 if instanceDigest != nil {
255254 return nil, fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
261260 return &is.reference
262261 }
263262
264 // LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
265 func (*tarballImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
263 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
264 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
265 // to read the image's layers.
266 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
267 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
268 // (e.g. if the source never returns manifest lists).
269 // The Digest field is guaranteed to be provided; Size may be -1.
270 // WARNING: The list may contain duplicates, and they are semantically relevant.
271 func (*tarballImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) {
266272 return nil, nil
267273 }
226226 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
227227 // (e.g. if the source never returns manifest lists).
228228 GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error)
229 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer blobsums that are listed in the image's manifest.
229 // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
230 // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
231 // to read the image's layers.
232 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
233 // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
234 // (e.g. if the source never returns manifest lists).
230235 // The Digest field is guaranteed to be provided; Size may be -1.
231236 // WARNING: The list may contain duplicates, and they are semantically relevant.
232 LayerInfosForCopy(ctx context.Context) ([]BlobInfo, error)
237 LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]BlobInfo, error)
233238 }
234239
235240 // ImageDestination is a service, possibly remote (= slow), to store components of a single image.
285290 // May use and/or update cache.
286291 TryReusingBlob(ctx context.Context, info BlobInfo, cache BlobInfoCache, canSubstitute bool) (bool, BlobInfo, error)
287292 // PutManifest writes manifest to the destination.
293 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write the manifest for
294 // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
295 // It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated
296 // by `manifest.Digest()`.
288297 // FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
289298 // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
290299 // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
291 PutManifest(ctx context.Context, manifest []byte) error
292 PutSignatures(ctx context.Context, signatures [][]byte) error
300 PutManifest(ctx context.Context, manifest []byte, instanceDigest *digest.Digest) error
301 // PutSignatures writes a set of signatures to the destination.
302 // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for
303 // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
304 // MUST be called after PutManifest (signatures may reference manifest contents).
305 PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error
293306 // Commit marks the process of storing the image as successful and asks for the image to be persisted.
294307 // WARNING: This does not have any transactional semantics:
295308 // - Uploaded data MAY be visible to others before Commit() is called
296309 // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
297 Commit(ctx context.Context) error
310 Commit(ctx context.Context, unparsedToplevel UnparsedImage) error
298311 }
299312
300313 // ManifestTypeRejectedError is returned by ImageDestination.PutManifest if the destination is in principle available,