Skip to content

Commit

Permalink
Close with error in all interfaces
Browse files Browse the repository at this point in the history
This allows us to provide in the image interfaces a method of providing
an error at close time. This is only currently used in a few situations.

Signed-off-by: Erik Hollensbe <github@hollensbe.org>
  • Loading branch information
Erik Hollensbe committed Feb 20, 2017
1 parent 3ff6510 commit 33ad61a
Show file tree
Hide file tree
Showing 16 changed files with 74 additions and 40 deletions.
45 changes: 31 additions & 14 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/image"
"github.com/containers/image/manifest"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
Expand Down Expand Up @@ -95,8 +94,15 @@ type Options struct {
DestinationCtx *types.SystemContext
}

// Image copies image from srcRef to destRef, using policyContext to validate source image admissibility.
func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) error {
// Image copies image from srcRef to destRef, using policyContext to validate
// source image admissibility.
func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (retErr error) {
// NOTE this function uses an output parameter for the error return value.
// Setting this and returning is the ideal way to return an error.
//
// the defers in this routine will wrap the error return with its own errors
// which can be valuable context in the middle of a multi-streamed copy.

reportWriter := ioutil.Discard
if options != nil && options.ReportWriter != nil {
reportWriter = options.ReportWriter
Expand All @@ -109,7 +115,12 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
if err != nil {
return errors.Wrapf(err, "Error initializing destination %s", transports.ImageName(destRef))
}
defer dest.Close()
defer func() {
if err := dest.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (dest: %v)", err)
}
}()

destSupportedManifestMIMETypes := dest.SupportedManifestMIMETypes()

rawSource, err := srcRef.NewImageSource(options.SourceCtx, destSupportedManifestMIMETypes)
Expand All @@ -119,7 +130,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
unparsedImage := image.UnparsedFromSource(rawSource)
defer func() {
if unparsedImage != nil {
unparsedImage.Close()
if err := unparsedImage.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (unparsed: %v)", err)
}
}
}()

Expand All @@ -129,10 +142,14 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
}
src, err := image.FromUnparsedImage(unparsedImage)
if err != nil {
return errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(srcRef))
retErr = errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(srcRef))
}
unparsedImage = nil
defer src.Close()
defer func() {
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (source: %v)", err)
}
}()

if src.IsMultiImage() {
return errors.Errorf("can not copy %s: manifest contains multiple images", transports.ImageName(srcRef))
Expand Down Expand Up @@ -371,7 +388,7 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest
// and returns a complete blobInfo of the copied blob and perhaps a <-chan diffIDResult if diffIDIsNeeded, to be read by the caller.
func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
diffIDIsNeeded bool) (types.BlobInfo, <-chan diffIDResult, error) {
var getDiffIDRecorder func(compression.DecompressorFunc) io.Writer // = nil
var getDiffIDRecorder func(decompressorFunc) io.Writer // = nil
var diffIDChan chan diffIDResult

err := errors.New("Internal error: unexpected panic in copyLayer") // For pipeWriter.CloseWithError below
Expand All @@ -382,7 +399,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl
pipeWriter.CloseWithError(err) // CloseWithError(nil) is equivalent to Close()
}()

getDiffIDRecorder = func(decompressor compression.DecompressorFunc) io.Writer {
getDiffIDRecorder = func(decompressor decompressorFunc) io.Writer {
// If this fails, e.g. because we have exited and due to pipeWriter.CloseWithError() above further
// reading from the pipe has failed, we don’t really care.
// We only read from diffIDChan if the rest of the flow has succeeded, and when we do read from it,
Expand All @@ -400,7 +417,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl
}

// diffIDComputationGoroutine reads all input from layerStream, uncompresses using decompressor if necessary, and sends its digest, and status, if any, to dest.
func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadCloser, decompressor compression.DecompressorFunc) {
func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadCloser, decompressor decompressorFunc) {
result := diffIDResult{
digest: "",
err: errors.New("Internal error: unexpected panic in diffIDComputationGoroutine"),
Expand All @@ -412,7 +429,7 @@ func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadClo
}

// computeDiffID reads all input from layerStream, uncompresses it using decompressor if necessary, and returns its digest.
func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc) (digest.Digest, error) {
func computeDiffID(stream io.Reader, decompressor decompressorFunc) (digest.Digest, error) {
if decompressor != nil {
s, err := decompressor(stream)
if err != nil {
Expand All @@ -429,7 +446,7 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc)
// perhaps compressing it if canCompress,
// and returns a complete blobInfo of the copied blob.
func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
getOriginalLayerCopyWriter func(decompressor compression.DecompressorFunc) io.Writer,
getOriginalLayerCopyWriter func(decompressor decompressorFunc) io.Writer,
canCompress bool) (types.BlobInfo, error) {
// The copying happens through a pipeline of connected io.Readers.
// === Input: srcStream
Expand All @@ -447,8 +464,8 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo
var destStream io.Reader = digestingReader

// === Detect compression of the input stream.
// This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by DetectCompression.
decompressor, destStream, err := compression.DetectCompression(destStream) // We could skip this in some cases, but let's keep the code path uniform
// This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by detectCompression.
decompressor, destStream, err := detectCompression(destStream) // We could skip this in some cases, but let's keep the code path uniform
if err != nil {
return types.BlobInfo{}, errors.Wrapf(err, "Error reading blob %s", srcInfo.Digest)
}
Expand Down
3 changes: 2 additions & 1 deletion directory/directory_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ func (d *dirImageDestination) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageDestination, if any.
func (d *dirImageDestination) Close() {
func (d *dirImageDestination) Close() error {
return nil
}

func (d *dirImageDestination) SupportedManifestMIMETypes() []string {
Expand Down
3 changes: 2 additions & 1 deletion directory/directory_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func (s *dirImageSource) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageSource, if any.
func (s *dirImageSource) Close() {
func (s *dirImageSource) Close() error {
return nil
}

// 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).
Expand Down
8 changes: 6 additions & 2 deletions docker/daemon/daemon_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
}

// Close removes resources associated with an initialized ImageDestination, if any.
func (d *daemonImageDestination) Close() {
func (d *daemonImageDestination) Close() error {
var err error
if !d.committed {
logrus.Debugf("docker-daemon: Closing tar stream to abort loading")
err = errors.New("Aborting upload, daemonImageDestination closed without a previous .Commit()")
// In principle, goroutineCancel() should abort the HTTP request and stop the process from continuing.
// In practice, though, various HTTP implementations used by client.Client.ImageLoad() (including
// https://github.com/golang/net/blob/master/context/ctxhttp/ctxhttp_pre17.go and the
Expand All @@ -104,9 +106,11 @@ func (d *daemonImageDestination) Close() {
// immediately, and hopefully, through terminating the sending which uses "Transfer-Encoding: chunked"" without sending
// the terminating zero-length chunk, prevent the docker daemon from processing the tar stream at all.
// Whether that works or not, closing the PipeWriter seems desirable in any case.
d.writer.CloseWithError(errors.New("Aborting upload, daemonImageDestination closed without a previous .Commit()"))
d.writer.CloseWithError(err)
}
d.goroutineCancel()

return err
}

// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
Expand Down
4 changes: 2 additions & 2 deletions docker/daemon/daemon_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ func (s *daemonImageSource) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageSource, if any.
func (s *daemonImageSource) Close() {
_ = os.Remove(s.tarCopyPath)
func (s *daemonImageSource) Close() error {
return os.Remove(s.tarCopyPath)
}

// tarReadCloser is a way to close the backing file of a tar.Reader when the user no longer needs the tar component.
Expand Down
3 changes: 2 additions & 1 deletion docker/docker_image_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func (d *dockerImageDestination) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageDestination, if any.
func (d *dockerImageDestination) Close() {
func (d *dockerImageDestination) Close() error {
return nil
}

func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
Expand Down
3 changes: 2 additions & 1 deletion docker/docker_image_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ func (s *dockerImageSource) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageSource, if any.
func (s *dockerImageSource) Close() {
func (s *dockerImageSource) Close() error {
return nil
}

// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
Expand Down
4 changes: 2 additions & 2 deletions image/docker_schema2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type unusedImageSource struct{}
func (f unusedImageSource) Reference() types.ImageReference {
panic("Unexpected call to a mock function")
}
func (f unusedImageSource) Close() {
func (f unusedImageSource) Close() error {
panic("Unexpected call to a mock function")
}
func (f unusedImageSource) GetManifest() ([]byte, string, error) {
Expand Down Expand Up @@ -346,7 +346,7 @@ type memoryImageDest struct {
func (d *memoryImageDest) Reference() types.ImageReference {
return refImageReferenceMock{d.ref}
}
func (d *memoryImageDest) Close() {
func (d *memoryImageDest) Close() error {
panic("Unexpected call to a mock function")
}
func (d *memoryImageDest) SupportedManifestMIMETypes() []string {
Expand Down
3 changes: 2 additions & 1 deletion image/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func (i *memoryImage) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized UnparsedImage, if any.
func (i *memoryImage) Close() {
func (i *memoryImage) Close() error {
return nil
}

// Size returns the size of the image as stored, if known, or -1 if not.
Expand Down
4 changes: 2 additions & 2 deletions image/unparsed.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func (i *UnparsedImage) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized UnparsedImage, if any.
func (i *UnparsedImage) Close() {
i.src.Close()
func (i *UnparsedImage) Close() error {
return i.src.Close()
}

// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
Expand Down
3 changes: 2 additions & 1 deletion oci/layout/oci_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func (d *ociImageDestination) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageDestination, if any.
func (d *ociImageDestination) Close() {
func (d *ociImageDestination) Close() error {
return nil
}

func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
Expand Down
3 changes: 2 additions & 1 deletion oci/layout/oci_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ func (s *ociImageSource) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageSource, if any.
func (s *ociImageSource) Close() {
func (s *ociImageSource) Close() error {
return nil
}

// 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).
Expand Down
12 changes: 8 additions & 4 deletions openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,15 @@ func (s *openshiftImageSource) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageSource, if any.
func (s *openshiftImageSource) Close() {
func (s *openshiftImageSource) Close() error {
if s.docker != nil {
s.docker.Close()
err := s.docker.Close()
s.docker = nil

return err
}

return nil
}

func (s *openshiftImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
Expand Down Expand Up @@ -329,8 +333,8 @@ func (d *openshiftImageDestination) Reference() types.ImageReference {
}

// Close removes resources associated with an initialized ImageDestination, if any.
func (d *openshiftImageDestination) Close() {
d.docker.Close()
func (d *openshiftImageDestination) Close() error {
return d.docker.Close()
}

func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
Expand Down
4 changes: 2 additions & 2 deletions signature/policy_reference_match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type refImageMock struct{ reference.Named }
func (ref refImageMock) Reference() types.ImageReference {
return refImageReferenceMock{ref.Named}
}
func (ref refImageMock) Close() {
func (ref refImageMock) Close() error {
panic("unexpected call to a mock function")
}
func (ref refImageMock) Manifest() ([]byte, string, error) {
Expand Down Expand Up @@ -322,7 +322,7 @@ type forbiddenImageMock struct{}
func (ref forbiddenImageMock) Reference() types.ImageReference {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Close() {
func (ref forbiddenImageMock) Close() error {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Manifest() ([]byte, string, error) {
Expand Down
6 changes: 4 additions & 2 deletions storage/storage_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,12 @@ func (s storageImageDestination) Reference() types.ImageReference {
return s.imageRef
}

func (s storageImageSource) Close() {
func (s storageImageSource) Close() error {
return nil
}

func (s storageImageDestination) Close() {
func (s storageImageDestination) Close() error {
return nil
}

func (s storageImageDestination) ShouldCompressLayers() bool {
Expand Down
6 changes: 3 additions & 3 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type ImageSource interface {
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
Reference() ImageReference
// Close removes resources associated with an initialized ImageSource, if any.
Close()
Close() error
// 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).
// It may use a remote (= slow) service.
GetManifest() ([]byte, string, error)
Expand Down Expand Up @@ -138,7 +138,7 @@ type ImageDestination interface {
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
Reference() ImageReference
// Close removes resources associated with an initialized ImageDestination, if any.
Close()
Close() error

// SupportedManifestMIMETypes tells which manifest mime types the destination supports
// If an empty slice or nil it's returned, then any mime type can be tried to upload
Expand Down Expand Up @@ -184,7 +184,7 @@ type UnparsedImage interface {
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
Reference() ImageReference
// Close removes resources associated with an initialized UnparsedImage, if any.
Close()
Close() error
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
Manifest() ([]byte, string, error)
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
Expand Down

0 comments on commit 33ad61a

Please sign in to comment.