Skip to content

Commit

Permalink
Add option to preserve manifests
Browse files Browse the repository at this point in the history
A digest-stable copy seems popular, even when not copying signed images.
Using --all can still change digests. Adding an option to ensure digests
are preserved.

See:
containers/skopeo#1440
containers/skopeo#1378
containers/skopeo#1102
containers/skopeo#1451

Signed-off-by: James Hewitt <[email protected]>
  • Loading branch information
Jamstah committed Nov 21, 2021
1 parent 080887f commit ae03c38
Showing 1 changed file with 11 additions and 7 deletions.
18 changes: 11 additions & 7 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ type Options struct {
ForceManifestMIMEType string
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
Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself

// Preserve digests, and fail if we cannot.
PreserveDigests bool

// If OciEncryptConfig is non-nil, it indicates that an image should be encrypted.
// The encryption options is derived from the construction of EncryptConfig object.
// Note: During initial encryption process of a layer, the resultant digest is not known
Expand Down Expand Up @@ -410,7 +414,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
return nil, errors.Wrapf(err, "Can not copy signatures to %s", transports.ImageName(c.dest.Reference()))
}
}
canModifyManifestList := (len(sigs) == 0)
canModifyManifestList := (len(sigs) == 0) && !options.PreserveDigests

// Determine if we'll need to convert the manifest list to a different format.
forceListMIMEType := options.ForceManifestMIMEType
Expand All @@ -426,7 +430,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
}
if selectedListType != originalList.MIMEType() {
if !canModifyManifestList {
return nil, errors.Errorf("manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", selectedListType)
return nil, errors.Errorf("manifest list must be converted to type %q to be written to destination, but we are preserving digests", selectedListType)
}
}

Expand Down Expand Up @@ -511,7 +515,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
// If we can't just use the original value, but we have to change it, flag an error.
if !bytes.Equal(attemptedManifestList, originalManifestList) {
if !canModifyManifestList {
return nil, errors.Errorf(" manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", thisListType)
return nil, errors.Errorf(" manifest list must be converted to type %q to be written to destination, but that would alter the digest", thisListType)
}
logrus.Debugf("Manifest list has been updated")
} else {
Expand Down Expand Up @@ -634,7 +638,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
src: src,
// diffIDsAreNeeded is computed later
canModifyManifest: len(sigs) == 0 && !destIsDigestedReference,
canModifyManifest: (len(sigs) == 0) && !destIsDigestedReference && !options.PreserveDigests,
ociEncryptLayers: options.OciEncryptLayers,
}
// Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
Expand Down Expand Up @@ -713,7 +717,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
// With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason,
// so let’s bail out early and with a better error message.
if !ic.canModifyManifest {
return nil, "", "", errors.Wrap(err, "Writing manifest failed (and converting it is not possible, image is signed or the destination specifies a digest)")
return nil, "", "", errors.Wrap(err, "Writing manifest failed and we are preserving digests so cannot try conversions")
}

// errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
Expand Down Expand Up @@ -814,7 +818,7 @@ func (ic *imageCopier) updateEmbeddedDockerReference() error {
}

if !ic.canModifyManifest {
return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest, which is not possible (image is signed or the destination specifies a digest)",
return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest and we are preserving digests",
transports.ImageName(ic.c.dest.Reference()), destRef.String())
}
ic.manifestUpdates.EmbeddedDockerReference = destRef
Expand Down Expand Up @@ -845,7 +849,7 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
// If we only need to check authorization, no updates required.
if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) {
if !ic.canModifyManifest {
return errors.Errorf("Copying this image requires changing layer representation, which is not possible (image is signed or the destination specifies a digest)")
return errors.Errorf("Copying this image requires changing layer representation and we are preserving digests")
}
srcInfos = updatedSrcInfos
srcInfosUpdated = true
Expand Down

0 comments on commit ae03c38

Please sign in to comment.