2017-05-17 17:18:35 +00:00
package copy
import (
"strings"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/pkg/errors"
2017-08-05 11:40:46 +00:00
"github.com/sirupsen/logrus"
2017-05-17 17:18:35 +00:00
)
// preferredManifestMIMETypes lists manifest MIME types in order of our preference, if we can't use the original manifest and need to convert.
// Prefer v2s2 to v2s1 because v2s2 does not need to be changed when uploading to a different location.
// Include v2s1 signed but not v2s1 unsigned, because docker/distribution requires a signature even if the unsigned MIME type is used.
var preferredManifestMIMETypes = [ ] string { manifest . DockerV2Schema2MediaType , manifest . DockerV2Schema1SignedMediaType }
// orderedSet is a list of strings (MIME types in our case), with each string appearing at most once.
type orderedSet struct {
list [ ] string
included map [ string ] struct { }
}
// newOrderedSet creates a correctly initialized orderedSet.
// [Sometimes it would be really nice if Golang had constructors…]
func newOrderedSet ( ) * orderedSet {
return & orderedSet {
list : [ ] string { } ,
included : map [ string ] struct { } { } ,
}
}
// append adds s to the end of os, only if it is not included already.
func ( os * orderedSet ) append ( s string ) {
if _ , ok := os . included [ s ] ; ! ok {
os . list = append ( os . list , s )
os . included [ s ] = struct { } { }
}
}
2017-11-03 17:36:13 +00:00
// determineManifestConversion updates ic.manifestUpdates to convert manifest to a supported MIME type, if necessary and ic.canModifyManifest.
// Note that the conversion will only happen later, through ic.src.UpdatedImage
2017-05-17 17:18:35 +00:00
// Returns the preferred manifest MIME type (whether we are converting to it or using it unmodified),
// and a list of other possible alternatives, in order.
2017-11-03 17:36:13 +00:00
func ( ic * imageCopier ) determineManifestConversion ( destSupportedManifestMIMETypes [ ] string , forceManifestMIMEType string ) ( string , [ ] string , error ) {
_ , srcType , err := ic . src . Manifest ( )
2017-05-17 17:18:35 +00:00
if err != nil { // This should have been cached?!
return "" , nil , errors . Wrap ( err , "Error reading manifest" )
}
2017-11-03 17:36:13 +00:00
if forceManifestMIMEType != "" {
destSupportedManifestMIMETypes = [ ] string { forceManifestMIMEType }
}
2017-05-17 17:18:35 +00:00
if len ( destSupportedManifestMIMETypes ) == 0 {
return srcType , [ ] string { } , nil // Anything goes; just use the original as is, do not try any conversions.
}
supportedByDest := map [ string ] struct { } { }
for _ , t := range destSupportedManifestMIMETypes {
supportedByDest [ t ] = struct { } { }
}
// destSupportedManifestMIMETypes is a static guess; a particular registry may still only support a subset of the types.
// So, build a list of types to try in order of decreasing preference.
// FIXME? This treats manifest.DockerV2Schema1SignedMediaType and manifest.DockerV2Schema1MediaType as distinct,
// although we are not really making any conversion, and it is very unlikely that a destination would support one but not the other.
// In practice, schema1 is probably the lowest common denominator, so we would expect to try the first one of the MIME types
// and never attempt the other one.
prioritizedTypes := newOrderedSet ( )
// First of all, prefer to keep the original manifest unmodified.
if _ , ok := supportedByDest [ srcType ] ; ok {
prioritizedTypes . append ( srcType )
}
2017-11-03 17:36:13 +00:00
if ! ic . canModifyManifest {
// We could also drop the !ic.canModifyManifest check and have the caller
2017-05-17 17:18:35 +00:00
// make the choice; it is already doing that to an extent, to improve error
2017-11-03 17:36:13 +00:00
// messages. But it is nice to hide the “if !ic.canModifyManifest, do no conversion”
2017-05-17 17:18:35 +00:00
// special case in here; the caller can then worry (or not) only about a good UI.
logrus . Debugf ( "We can't modify the manifest, hoping for the best..." )
return srcType , [ ] string { } , nil // Take our chances - FIXME? Or should we fail without trying?
}
// Then use our list of preferred types.
for _ , t := range preferredManifestMIMETypes {
if _ , ok := supportedByDest [ t ] ; ok {
prioritizedTypes . append ( t )
}
}
// Finally, try anything else the destination supports.
for _ , t := range destSupportedManifestMIMETypes {
prioritizedTypes . append ( t )
}
logrus . Debugf ( "Manifest has MIME type %s, ordered candidate list [%s]" , srcType , strings . Join ( prioritizedTypes . list , ", " ) )
if len ( prioritizedTypes . list ) == 0 { // Coverage: destSupportedManifestMIMETypes is not empty (or we would have exited in the “Anything goes” case above), so this should never happen.
return "" , nil , errors . New ( "Internal error: no candidate MIME types" )
}
preferredType := prioritizedTypes . list [ 0 ]
if preferredType != srcType {
2017-11-03 17:36:13 +00:00
ic . manifestUpdates . ManifestMIMEType = preferredType
2017-05-17 17:18:35 +00:00
} else {
logrus . Debugf ( "... will first try using the original manifest unmodified" )
}
return preferredType , prioritizedTypes . list [ 1 : ] , nil
}
2017-11-03 17:36:13 +00:00
// isMultiImage returns true if img is a list of images
func isMultiImage ( img types . UnparsedImage ) ( bool , error ) {
_ , mt , err := img . Manifest ( )
if err != nil {
return false , err
}
return manifest . MIMETypeIsMultiImage ( mt ) , nil
}