-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#305 cleanup 2: Reference handling #426
Changes from all commits
c395f15
cce3881
225e91a
b8969b7
04e1018
88567cf
5cb0e19
a758135
cbb7c07
e75d430
a0db144
50232c6
63f2cf8
52e9a63
c0bc991
4406711
5b62b7d
acf13f4
5ad5ea2
91508be
d801ec6
70afa3b
5bc7884
059c0fc
841cbfe
446237b
1faf4fa
e78daf9
682fd27
3ebfd20
46174f3
416ef90
e643db3
c5e28a6
9d0ef1b
896003d
e17ff8f
03e1320
034fb34
b69da33
9fd3ee4
9b4590b
62ed2a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,8 +50,7 @@ type storageImageSource struct { | |
} | ||
|
||
type storageImageDestination struct { | ||
imageRef storageReference // The reference we'll use to name the image | ||
publicRef storageReference // The reference we return when asked about the name we'll give to the image | ||
imageRef storageReference | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Do we need a comment here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. “A reference to the image this destination writes to” should be clear enough without a comment; the previous two values needed comments (even more comments, arguably) to explain when to use which value. |
||
directory string // Temporary directory where we store blobs until Commit() time | ||
nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs | ||
manifest []byte // Manifest contents, temporary | ||
|
@@ -243,15 +242,8 @@ func newImageDestination(imageRef storageReference) (*storageImageDestination, e | |
if err != nil { | ||
return nil, errors.Wrapf(err, "error creating a temporary directory") | ||
} | ||
// Break reading of the reference we're writing, so that copy.Image() won't try to rewrite | ||
// schema1 image manifests to remove embedded references, since that changes the manifest's | ||
// digest, and that makes the image unusable if we subsequently try to access it using a | ||
// reference that mentions the no-longer-correct digest. | ||
publicRef := imageRef | ||
publicRef.name = nil | ||
image := &storageImageDestination{ | ||
imageRef: imageRef, | ||
publicRef: publicRef, | ||
directory: directory, | ||
blobDiffIDs: make(map[digest.Digest]digest.Digest), | ||
fileSizes: make(map[digest.Digest]int64), | ||
|
@@ -261,11 +253,10 @@ func newImageDestination(imageRef storageReference) (*storageImageDestination, e | |
return image, nil | ||
} | ||
|
||
// Reference returns a mostly-usable image reference that can't return a DockerReference, to | ||
// avoid triggering logic in copy.Image() that rewrites schema 1 image manifests in order to | ||
// remove image names that they contain which don't match the value we're using. | ||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent, | ||
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects. | ||
func (s storageImageDestination) Reference() types.ImageReference { | ||
return s.publicRef | ||
return s.imageRef | ||
} | ||
|
||
// Close cleans up the temporary directory. | ||
|
@@ -613,7 +604,7 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { | |
if name := s.imageRef.DockerReference(); len(oldNames) > 0 || name != nil { | ||
names := []string{} | ||
if name != nil { | ||
names = append(names, verboseName(name)) | ||
names = append(names, name.String()) | ||
} | ||
if len(oldNames) > 0 { | ||
names = append(names, oldNames...) | ||
|
@@ -703,6 +694,13 @@ func (s *storageImageDestination) MustMatchRuntimeOS() bool { | |
return true | ||
} | ||
|
||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(), | ||
// and would prefer to receive an unmodified manifest instead of one modified for the destination. | ||
// Does not make a difference if Reference().DockerReference() is nil. | ||
func (s *storageImageDestination) IgnoresEmbeddedDockerReference() bool { | ||
return true // Yes, we want the unmodified manifest | ||
} | ||
|
||
// PutSignatures records the image's signatures for committing as a single data blob. | ||
func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { | ||
sizes := []int{} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,63 +9,72 @@ import ( | |
"github.com/containers/image/docker/reference" | ||
"github.com/containers/image/types" | ||
"github.com/containers/storage" | ||
digest "github.com/opencontainers/go-digest" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// A storageReference holds an arbitrary name and/or an ID, which is a 32-byte | ||
// value hex-encoded into a 64-character string, and a reference to a Store | ||
// where an image is, or would be, kept. | ||
// Either "named" or "id" must be set. | ||
type storageReference struct { | ||
transport storageTransport | ||
reference string | ||
named reference.Named // may include a tag and/or a digest | ||
id string | ||
name reference.Named | ||
tag string | ||
digest digest.Digest | ||
} | ||
|
||
func newReference(transport storageTransport, reference, id string, name reference.Named, tag string, digest digest.Digest) *storageReference { | ||
func newReference(transport storageTransport, named reference.Named, id string) (*storageReference, error) { | ||
if named == nil && id == "" { | ||
return nil, ErrInvalidReference | ||
} | ||
// We take a copy of the transport, which contains a pointer to the | ||
// store that it used for resolving this reference, so that the | ||
// transport that we'll return from Transport() won't be affected by | ||
// further calls to the original transport's SetStore() method. | ||
return &storageReference{ | ||
transport: transport, | ||
reference: reference, | ||
named: named, | ||
id: id, | ||
name: name, | ||
tag: tag, | ||
digest: digest, | ||
}, nil | ||
} | ||
|
||
// imageMatchesRepo returns true iff image.Names contains an element with the same repo as ref | ||
func imageMatchesRepo(image *storage.Image, ref reference.Named) bool { | ||
repo := ref.Name() | ||
for _, name := range image.Names { | ||
if named, err := reference.ParseNormalizedNamed(name); err == nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok to ignore the err? Should we logrus.Debugf this or is this expected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(This code is not new, it’s only moved out of the calling function (commit “UNTESTED: Extract duplicate code from resolveImage”).) |
||
if named.Name() == repo { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Resolve the reference's name to an image ID in the store, if there's already | ||
// one present with the same name or ID, and return the image. | ||
func (s *storageReference) resolveImage() (*storage.Image, error) { | ||
var loadedImage *storage.Image | ||
if s.id == "" { | ||
// Look for an image that has the expanded reference name as an explicit Name value. | ||
image, err := s.transport.store.Image(s.reference) | ||
image, err := s.transport.store.Image(s.named.String()) | ||
if image != nil && err == nil { | ||
loadedImage = image | ||
s.id = image.ID | ||
} | ||
} | ||
if s.id == "" && s.name != nil && s.digest != "" { | ||
// Look for an image with the specified digest that has the same name, | ||
// though possibly with a different tag or digest, as a Name value, so | ||
// that the canonical reference can be implicitly resolved to the image. | ||
images, err := s.transport.store.ImagesByDigest(s.digest) | ||
if images != nil && err == nil { | ||
repo := reference.FamiliarName(reference.TrimNamed(s.name)) | ||
search: | ||
for _, image := range images { | ||
for _, name := range image.Names { | ||
if named, err := reference.ParseNormalizedNamed(name); err == nil { | ||
if reference.FamiliarName(reference.TrimNamed(named)) == repo { | ||
s.id = image.ID | ||
break search | ||
} | ||
if s.id == "" && s.named != nil { | ||
if digested, ok := s.named.(reference.Digested); ok { | ||
// Look for an image with the specified digest that has the same name, | ||
// though possibly with a different tag or digest, as a Name value, so | ||
// that the canonical reference can be implicitly resolved to the image. | ||
images, err := s.transport.store.ImagesByDigest(digested.Digest()) | ||
if images != nil && err == nil { | ||
for _, image := range images { | ||
if imageMatchesRepo(image, s.named) { | ||
loadedImage = image | ||
s.id = image.ID | ||
break | ||
} | ||
} | ||
} | ||
|
@@ -75,27 +84,20 @@ func (s *storageReference) resolveImage() (*storage.Image, error) { | |
logrus.Debugf("reference %q does not resolve to an image ID", s.StringWithinTransport()) | ||
return nil, errors.Wrapf(ErrNoSuchImage, "reference %q does not resolve to an image ID", s.StringWithinTransport()) | ||
} | ||
img, err := s.transport.store.Image(s.id) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "error reading image %q", s.id) | ||
} | ||
if s.name != nil { | ||
repo := reference.FamiliarName(reference.TrimNamed(s.name)) | ||
nameMatch := false | ||
for _, name := range img.Names { | ||
if named, err := reference.ParseNormalizedNamed(name); err == nil { | ||
if reference.FamiliarName(reference.TrimNamed(named)) == repo { | ||
nameMatch = true | ||
break | ||
} | ||
} | ||
if loadedImage == nil { | ||
img, err := s.transport.store.Image(s.id) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "error reading image %q", s.id) | ||
} | ||
if !nameMatch { | ||
loadedImage = img | ||
} | ||
if s.named != nil { | ||
if !imageMatchesRepo(loadedImage, s.named) { | ||
logrus.Errorf("no image matching reference %q found", s.StringWithinTransport()) | ||
return nil, ErrNoSuchImage | ||
} | ||
} | ||
return img, nil | ||
return loadedImage, nil | ||
} | ||
|
||
// Return a Transport object that defaults to using the same store that we used | ||
|
@@ -110,20 +112,7 @@ func (s storageReference) Transport() types.ImageTransport { | |
|
||
// Return a name with a tag or digest, if we have either, else return it bare. | ||
func (s storageReference) DockerReference() reference.Named { | ||
if s.name == nil { | ||
return nil | ||
} | ||
if s.tag != "" { | ||
if namedTagged, err := reference.WithTag(s.name, s.tag); err == nil { | ||
return namedTagged | ||
} | ||
} | ||
if s.digest != "" { | ||
if canonical, err := reference.WithDigest(s.name, s.digest); err == nil { | ||
return canonical | ||
} | ||
} | ||
return s.name | ||
return s.named | ||
} | ||
|
||
// Return a name with a tag, prefixed with the graph root and driver name, to | ||
|
@@ -135,25 +124,25 @@ func (s storageReference) StringWithinTransport() string { | |
if len(options) > 0 { | ||
optionsList = ":" + strings.Join(options, ",") | ||
} | ||
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "+" + s.transport.store.RunRoot() + optionsList + "]" | ||
if s.reference == "" { | ||
return storeSpec + "@" + s.id | ||
res := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "+" + s.transport.store.RunRoot() + optionsList + "]" | ||
if s.named != nil { | ||
res = res + s.named.String() | ||
} | ||
if s.id == "" { | ||
return storeSpec + s.reference | ||
if s.id != "" { | ||
res = res + "@" + s.id | ||
} | ||
return storeSpec + s.reference + "@" + s.id | ||
return res | ||
} | ||
|
||
func (s storageReference) PolicyConfigurationIdentity() string { | ||
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]" | ||
if s.name == nil { | ||
return storeSpec + "@" + s.id | ||
res := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]" | ||
if s.named != nil { | ||
res = res + s.named.String() | ||
} | ||
if s.id == "" { | ||
return storeSpec + s.reference | ||
if s.id != "" { | ||
res = res + "@" + s.id | ||
} | ||
return storeSpec + s.reference + "@" + s.id | ||
return res | ||
} | ||
|
||
// Also accept policy that's tied to the combination of the graph root and | ||
|
@@ -164,9 +153,17 @@ func (s storageReference) PolicyConfigurationNamespaces() []string { | |
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]" | ||
driverlessStoreSpec := "[" + s.transport.store.GraphRoot() + "]" | ||
namespaces := []string{} | ||
if s.name != nil { | ||
name := reference.TrimNamed(s.name) | ||
components := strings.Split(name.String(), "/") | ||
if s.named != nil { | ||
if s.id != "" { | ||
// The reference without the ID is also a valid namespace. | ||
namespaces = append(namespaces, storeSpec+s.named.String()) | ||
} | ||
tagged, isTagged := s.named.(reference.Tagged) | ||
_, isDigested := s.named.(reference.Digested) | ||
if isTagged && isDigested { // s.named is "name:tag@digest"; add a "name:tag" parent namespace. | ||
namespaces = append(namespaces, storeSpec+s.named.Name()+":"+tagged.Tag()) | ||
} | ||
components := strings.Split(s.named.Name(), "/") | ||
for len(components) > 0 { | ||
namespaces = append(namespaces, storeSpec+strings.Join(components, "/")) | ||
components = components[:len(components)-1] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when it is not used, should it return
true
so that updateEmbeddedDockerReference exits early?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@giuseppe I’m not too worried about saving a few cycles during an image copy which writes gigabytes of data to the disk.
If anything, it’s more a matter of semantics: does the
ostree
backend prefer a manifest tailored for it, or an unmodified one? Looking at howostreeImageDestination.Commit
records the manifest digest in theostree
metadata, maybe it really would prefer the unmodified one, and this should returntrue
for that reason.If it does not matter at all, I’d rather leave it at the default
false
; we may eventually want to to have defaults for many of theImageDestination
flags without copies in every transport, and that will result in shorter code if more transports use the default values.