d76645680f
Bump containers/image (pulling in its new dependency on ostree-go), containers/storage, and updated image-spec. This pulls in the OCI v1.0 specifications and code that allows us to support 1.0 images. Signed-off-by: Dan Walsh <dwalsh@redhat.com> Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
128 lines
5 KiB
Go
128 lines
5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"io"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/containers/image/docker/reference"
|
|
"github.com/containers/image/docker/tarfile"
|
|
"github.com/containers/image/types"
|
|
"github.com/docker/docker/client"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type daemonImageDestination struct {
|
|
ref daemonReference
|
|
*tarfile.Destination // Implements most of types.ImageDestination
|
|
// For talking to imageLoadGoroutine
|
|
goroutineCancel context.CancelFunc
|
|
statusChannel <-chan error
|
|
writer *io.PipeWriter
|
|
// Other state
|
|
committed bool // writer has been closed
|
|
}
|
|
|
|
// newImageDestination returns a types.ImageDestination for the specified image reference.
|
|
func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
|
|
if ref.ref == nil {
|
|
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
|
}
|
|
namedTaggedRef, ok := ref.ref.(reference.NamedTagged)
|
|
if !ok {
|
|
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
|
}
|
|
|
|
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error initializing docker engine client")
|
|
}
|
|
|
|
reader, writer := io.Pipe()
|
|
// Commit() may never be called, so we may never read from this channel; so, make this buffered to allow imageLoadGoroutine to write status and terminate even if we never read it.
|
|
statusChannel := make(chan error, 1)
|
|
|
|
ctx, goroutineCancel := context.WithCancel(context.Background())
|
|
go imageLoadGoroutine(ctx, c, reader, statusChannel)
|
|
|
|
return &daemonImageDestination{
|
|
ref: ref,
|
|
Destination: tarfile.NewDestination(writer, namedTaggedRef),
|
|
goroutineCancel: goroutineCancel,
|
|
statusChannel: statusChannel,
|
|
writer: writer,
|
|
committed: false,
|
|
}, nil
|
|
}
|
|
|
|
// imageLoadGoroutine accepts tar stream on reader, sends it to c, and reports error or success by writing to statusChannel
|
|
func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeReader, statusChannel chan<- error) {
|
|
err := errors.New("Internal error: unexpected panic in imageLoadGoroutine")
|
|
defer func() {
|
|
logrus.Debugf("docker-daemon: sending done, status %v", err)
|
|
statusChannel <- err
|
|
}()
|
|
defer func() {
|
|
if err == nil {
|
|
reader.Close()
|
|
} else {
|
|
reader.CloseWithError(err)
|
|
}
|
|
}()
|
|
|
|
resp, err := c.ImageLoad(ctx, reader, true)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "Error saving image to docker engine")
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
}
|
|
|
|
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
|
func (d *daemonImageDestination) MustMatchRuntimeOS() bool {
|
|
return true
|
|
}
|
|
|
|
// Close removes resources associated with an initialized ImageDestination, if any.
|
|
func (d *daemonImageDestination) Close() error {
|
|
if !d.committed {
|
|
logrus.Debugf("docker-daemon: Closing tar stream to abort loading")
|
|
// 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
|
|
// net/http version with native Context support in Go 1.7) do not always actually immediately cancel
|
|
// the operation: they may process the HTTP request, or a part of it, to completion in a goroutine, and
|
|
// return early if the context is canceled without terminating the goroutine at all.
|
|
// So we need this CloseWithError to terminate sending the HTTP request Body
|
|
// 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.goroutineCancel()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *daemonImageDestination) Reference() types.ImageReference {
|
|
return d.ref
|
|
}
|
|
|
|
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
|
// WARNING: This does not have any transactional semantics:
|
|
// - Uploaded data MAY be visible to others before Commit() is called
|
|
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
|
func (d *daemonImageDestination) Commit() error {
|
|
logrus.Debugf("docker-daemon: Closing tar stream")
|
|
if err := d.Destination.Commit(); err != nil {
|
|
return err
|
|
}
|
|
if err := d.writer.Close(); err != nil {
|
|
return err
|
|
}
|
|
d.committed = true // We may still fail, but we are done sending to imageLoadGoroutine.
|
|
|
|
logrus.Debugf("docker-daemon: Waiting for status")
|
|
err := <-d.statusChannel
|
|
return err
|
|
}
|