From 52f4d09ffb376ffaa6677cb1e0413c6a97f53f24 Mon Sep 17 00:00:00 2001 From: John Howard Date: Thu, 11 Jun 2015 11:29:29 -0700 Subject: [PATCH] Windows: Graph driver implementation Signed-off-by: John Howard --- builder/internals.go | 13 +- cliconfig/config.go | 3 +- daemon/commit.go | 4 +- daemon/container.go | 22 +- .../{container_linux.go => container_unix.go} | 13 +- daemon/container_windows.go | 56 +++ daemon/create.go | 3 +- daemon/daemon.go | 7 +- daemon/daemon_windows.go | 35 +- daemon/graphdriver/driver.go | 1 + daemon/graphdriver/driver_windows.go | 16 +- daemon/graphdriver/windows/windows.go | 326 ++++++++++++++++++ daemon/image_delete.go | 3 +- daemon/volumes.go | 3 +- docker/daemon.go | 3 +- graph/graph.go | 99 +++--- graph/graph_test.go | 12 +- graph/graph_unix.go | 114 ++++++ graph/graph_windows.go | 160 +++++++++ graph/history.go | 13 +- graph/list.go | 3 +- graph/load.go | 2 +- graph/manifest_test.go | 5 +- graph/pull.go | 9 +- graph/push.go | 5 +- graph/service.go | 23 +- graph/tags.go | 7 +- graph/tags_unit_test.go | 5 +- image/image.go | 30 -- 29 files changed, 850 insertions(+), 145 deletions(-) rename daemon/{container_linux.go => container_unix.go} (99%) create mode 100644 daemon/graphdriver/windows/windows.go create mode 100644 graph/graph_unix.go create mode 100644 graph/graph_windows.go diff --git a/builder/internals.go b/builder/internals.go index e868a47bdc..0ec75143a3 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -24,7 +24,6 @@ import ( "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/graph" - imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/httputils" @@ -229,12 +228,20 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp } defer container.Unmount() + if err := container.PrepareStorage(); err != nil { + return err + } + for _, ci := range copyInfos { if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil { return err } } + if err := container.CleanupStorage(); err != nil { + return err + } + if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil { return err } @@ -455,7 +462,7 @@ func ContainsWildcards(name string) bool { return false } -func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { +func (b *Builder) pullImage(name string) (*graph.Image, error) { remote, tag := parsers.ParseRepositoryTag(name) if tag == "" { tag = "latest" @@ -493,7 +500,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { return image, nil } -func (b *Builder) processImageFrom(img *imagepkg.Image) error { +func (b *Builder) processImageFrom(img *graph.Image) error { b.image = img.ID if img.Config != nil { diff --git a/cliconfig/config.go b/cliconfig/config.go index 10f24f3c8e..10431fd261 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/pkg/system" ) const ( @@ -177,7 +178,7 @@ func (configFile *ConfigFile) Save() error { return err } - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { + if err := system.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { return err } diff --git a/daemon/commit.go b/daemon/commit.go index 5921d77e0f..5321bef865 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -1,7 +1,7 @@ package daemon import ( - "github.com/docker/docker/image" + "github.com/docker/docker/graph" "github.com/docker/docker/runconfig" ) @@ -16,7 +16,7 @@ type ContainerCommitConfig struct { // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (daemon *Daemon) Commit(container *Container, c *ContainerCommitConfig) (*image.Image, error) { +func (daemon *Daemon) Commit(container *Container, c *ContainerCommitConfig) (*graph.Image, error) { if c.Pause && !container.IsPaused() { container.Pause() defer container.Unpause() diff --git a/daemon/container.go b/daemon/container.go index f5237791ac..37e75fe888 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -20,7 +20,7 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/daemon/network" - "github.com/docker/docker/image" + "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/fileutils" @@ -259,6 +259,13 @@ func (container *Container) Start() (err error) { if err := container.Mount(); err != nil { return err } + + // No-op if non-Windows. Once the container filesystem is mounted, + // prepare the layer to boot using the Windows driver. + if err := container.PrepareStorage(); err != nil { + return err + } + if err := container.initializeNetworking(); err != nil { return err } @@ -350,6 +357,10 @@ func (container *Container) cleanup() { disableAllActiveLinks(container) + if err := container.CleanupStorage(); err != nil { + logrus.Errorf("%v: Failed to cleanup storage: %v", container.ID, err) + } + if err := container.Unmount(); err != nil { logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err) } @@ -573,7 +584,7 @@ func (container *Container) Changes() ([]archive.Change, error) { return container.changes() } -func (container *Container) GetImage() (*image.Image, error) { +func (container *Container) GetImage() (*graph.Image, error) { if container.daemon == nil { return nil, fmt.Errorf("Can't get image of unregistered container") } @@ -666,8 +677,15 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { if err != nil { return nil, err } + + if err := container.PrepareStorage(); err != nil { + container.Unmount() + return nil, err + } + reader := ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() + container.CleanupStorage() container.UnmountVolumes(true) container.Unmount() return err diff --git a/daemon/container_linux.go b/daemon/container_unix.go similarity index 99% rename from daemon/container_linux.go rename to daemon/container_unix.go index 8c5b15be0a..766e9b37c3 100644 --- a/daemon/container_linux.go +++ b/daemon/container_unix.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package daemon @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" @@ -970,7 +971,7 @@ func (container *Container) setupWorkingDirectory() error { return err } - if err := os.MkdirAll(pth, 0755); err != nil { + if err := system.MkdirAll(pth, 0755); err != nil { return err } } @@ -1109,3 +1110,11 @@ func (container *Container) UnmountVolumes(forceSyscall bool) error { return nil } + +func (container *Container) PrepareStorage() error { + return nil +} + +func (container *Container) CleanupStorage() error { + return nil +} diff --git a/daemon/container_windows.go b/daemon/container_windows.go index 502572d84e..718ca47ff5 100644 --- a/daemon/container_windows.go +++ b/daemon/container_windows.go @@ -4,10 +4,14 @@ package daemon import ( "fmt" + "path/filepath" "strings" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/daemon/graphdriver/windows" + "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" + "github.com/microsoft/hcsshim" ) // This is deliberately empty on Windows as the default path will be set by @@ -102,6 +106,26 @@ func populateCommand(c *Container, env []string) error { processConfig.Env = env + var layerFolder string + var layerPaths []string + + // The following is specific to the Windows driver. We do this to + // enable VFS to continue operating for development purposes. + if wd, ok := c.daemon.driver.(*windows.WindowsGraphDriver); ok { + var err error + var img *graph.Image + var ids []string + + if img, err = c.daemon.graph.Get(c.ImageID); err != nil { + return fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err) + } + if ids, err = c.daemon.graph.ParentLayerIds(img); err != nil { + return fmt.Errorf("Failed to get parentlayer ids %s", img.ID) + } + layerPaths = wd.LayerIdsToPaths(ids) + layerFolder = filepath.Join(wd.Info().HomeDir, filepath.Base(c.ID)) + } + // TODO Windows: Factor out remainder of unused fields. c.command = &execdriver.Command{ ID: c.ID, @@ -118,6 +142,8 @@ func populateCommand(c *Container, env []string) error { ProcessLabel: c.GetProcessLabel(), MountLabel: c.GetMountLabel(), FirstStart: !c.HasBeenStartedBefore, + LayerFolder: layerFolder, + LayerPaths: layerPaths, } return nil @@ -165,3 +191,33 @@ func (container *Container) DisableLink(name string) { func (container *Container) UnmountVolumes(forceSyscall bool) error { return nil } + +func (container *Container) PrepareStorage() error { + if wd, ok := container.daemon.driver.(*windows.WindowsGraphDriver); ok { + // Get list of paths to parent layers. + var ids []string + if container.ImageID != "" { + img, err := container.daemon.graph.Get(container.ImageID) + if err != nil { + return err + } + + ids, err = container.daemon.graph.ParentLayerIds(img) + if err != nil { + return err + } + } + + if err := hcsshim.PrepareLayer(wd.Info(), container.ID, wd.LayerIdsToPaths(ids)); err != nil { + return err + } + } + return nil +} + +func (container *Container) CleanupStorage() error { + if wd, ok := container.daemon.driver.(*windows.WindowsGraphDriver); ok { + return hcsshim.UnprepareLayer(wd.Info(), container.ID) + } + return nil +} diff --git a/daemon/create.go b/daemon/create.go index d4f0db984f..6d55028b72 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -8,7 +8,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/graph" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" @@ -47,7 +46,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos var ( container *Container warnings []string - img *image.Image + img *graph.Image imgID string err error ) diff --git a/daemon/daemon.go b/daemon/daemon.go index 26e9e039b4..21fea69142 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -23,7 +23,6 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/network" "github.com/docker/docker/graph" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/graphdb" @@ -338,7 +337,7 @@ func (daemon *Daemon) restore() error { return nil } -func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) error { +func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *graph.Image) error { if img != nil && img.Config != nil { if err := runconfig.Merge(config, img.Config); err != nil { return err @@ -879,7 +878,7 @@ func (daemon *Daemon) ContainerGraph() *graphdb.Database { return daemon.containerGraph } -func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) { +func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*graph.Image, error) { // Retrieve all images images, err := daemon.Graph().Map() if err != nil { @@ -896,7 +895,7 @@ func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*i } // Loop on the children of the given image and check the config - var match *image.Image + var match *graph.Image for elem := range imageMap[imgID] { img, ok := images[elem] if !ok { diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 5bc8d8cba3..1000beefc0 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -6,11 +6,14 @@ import ( "runtime" "syscall" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/docker/daemon/graphdriver/windows" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/runconfig" "github.com/docker/libnetwork" + "github.com/microsoft/hcsshim" ) func (daemon *Daemon) Changes(container *Container) ([]archive.Change, error) { @@ -31,8 +34,36 @@ func (daemon *Daemon) createRootfs(container *Container) error { if err := os.Mkdir(container.root, 0700); err != nil { return err } - if err := daemon.driver.Create(container.ID, container.ImageID); err != nil { - return err + + if wd, ok := daemon.driver.(*windows.WindowsGraphDriver); ok { + if container.ImageID != "" { + // Get list of paths to parent layers. + logrus.Debugln("createRootfs: Container has parent image:", container.ImageID) + img, err := daemon.graph.Get(container.ImageID) + if err != nil { + return err + } + + ids, err := daemon.graph.ParentLayerIds(img) + if err != nil { + return err + } + logrus.Debugf("Got image ids: %d", len(ids)) + + if err := hcsshim.CreateSandboxLayer(wd.Info(), container.ID, container.ImageID, wd.LayerIdsToPaths(ids)); err != nil { + return err + } + } else { + if err := daemon.driver.Create(container.ID, container.ImageID); err != nil { + return err + } + } + } else { + // Fall-back code path to allow the use of the VFS driver for development + if err := daemon.driver.Create(container.ID, container.ImageID); err != nil { + return err + } + } return nil } diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 906571dfc8..b7e35e4c0d 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -101,6 +101,7 @@ func GetDriver(name, home string, options []string) (Driver, error) { if initFunc, exists := drivers[name]; exists { return initFunc(filepath.Join(home, name), options) } + logrus.Errorf("Failed to GetDriver graph %s %s", name, home) return nil, ErrNotSupported } diff --git a/daemon/graphdriver/driver_windows.go b/daemon/graphdriver/driver_windows.go index 574b357c8d..387f666c54 100644 --- a/daemon/graphdriver/driver_windows.go +++ b/daemon/graphdriver/driver_windows.go @@ -1,14 +1,24 @@ package graphdriver -type DiffDiskDriver interface { +import ( + "github.com/docker/docker/pkg/archive" + "github.com/microsoft/hcsshim" +) + +type WindowsGraphDriver interface { Driver - CopyDiff(id, sourceId string) error + CopyDiff(id, sourceId string, parentLayerPaths []string) error + LayerIdsToPaths(ids []string) []string + Info() hcsshim.DriverInfo + Export(id string, parentLayerPaths []string) (archive.Archive, error) + Import(id string, layerData archive.ArchiveReader, parentLayerPaths []string) (int64, error) } var ( // Slice of drivers that should be used in order priority = []string{ - "windows", + "windowsfilter", + "windowsdiff", "vfs", } ) diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go new file mode 100644 index 0000000000..7e7c592719 --- /dev/null +++ b/daemon/graphdriver/windows/windows.go @@ -0,0 +1,326 @@ +//+build windows + +package windows + +import ( + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/ioutils" + "github.com/microsoft/hcsshim" +) + +func init() { + graphdriver.Register("windowsfilter", InitFilter) + graphdriver.Register("windowsdiff", InitDiff) +} + +const ( + diffDriver = iota + filterDriver +) + +type WindowsGraphDriver struct { + info hcsshim.DriverInfo + sync.Mutex // Protects concurrent modification to active + active map[string]int +} + +// New returns a new Windows storage filter driver. +func InitFilter(home string, options []string) (graphdriver.Driver, error) { + logrus.Debugf("WindowsGraphDriver InitFilter at %s", home) + d := &WindowsGraphDriver{ + info: hcsshim.DriverInfo{ + HomeDir: home, + Flavour: filterDriver, + }, + active: make(map[string]int), + } + return d, nil +} + +// New returns a new Windows differencing disk driver. +func InitDiff(home string, options []string) (graphdriver.Driver, error) { + logrus.Debugf("WindowsGraphDriver InitDiff at %s", home) + d := &WindowsGraphDriver{ + info: hcsshim.DriverInfo{ + HomeDir: home, + Flavour: diffDriver, + }, + active: make(map[string]int), + } + return d, nil +} + +func (d *WindowsGraphDriver) Info() hcsshim.DriverInfo { + return d.info +} + +func (d *WindowsGraphDriver) String() string { + switch d.info.Flavour { + case diffDriver: + return "windowsdiff" + case filterDriver: + return "windowsfilter" + default: + return "Unknown driver flavour" + } +} + +func (d *WindowsGraphDriver) Status() [][2]string { + return [][2]string{ + {"Windows", ""}, + } +} + +// Exists returns true if the given id is registered with +// this driver +func (d *WindowsGraphDriver) Exists(id string) bool { + result, err := hcsshim.LayerExists(d.info, id) + if err != nil { + return false + } + return result +} + +func (d *WindowsGraphDriver) Create(id, parent string) error { + return hcsshim.CreateLayer(d.info, id, parent) +} + +func (d *WindowsGraphDriver) dir(id string) string { + return filepath.Join(d.info.HomeDir, filepath.Base(id)) +} + +// Remove unmounts and removes the dir information +func (d *WindowsGraphDriver) Remove(id string) error { + return hcsshim.DestroyLayer(d.info, id) +} + +// Get returns the rootfs path for the id. This will mount the dir at it's given path +func (d *WindowsGraphDriver) Get(id, mountLabel string) (string, error) { + var dir string + + d.Lock() + defer d.Unlock() + + if d.active[id] == 0 { + if err := hcsshim.ActivateLayer(d.info, id); err != nil { + return "", err + } + } + + mountPath, err := hcsshim.GetLayerMountPath(d.info, id) + if err != nil { + return "", err + } + + // If the layer has a mount path, use that. Otherwise, use the + // folder path. + if mountPath != "" { + dir = mountPath + } else { + dir = d.dir(id) + } + + d.active[id]++ + + return dir, nil +} + +func (d *WindowsGraphDriver) Put(id string) error { + logrus.Debugf("WindowsGraphDriver Put() id %s", id) + + d.Lock() + defer d.Unlock() + + if d.active[id] > 1 { + d.active[id]-- + } else if d.active[id] == 1 { + if err := hcsshim.DeactivateLayer(d.info, id); err != nil { + return err + } + delete(d.active, id) + } + + return nil +} + +func (d *WindowsGraphDriver) Cleanup() error { + return nil +} + +// Diff produces an archive of the changes between the specified +// layer and its parent layer which may be "". +func (d *WindowsGraphDriver) Diff(id, parent string) (arch archive.Archive, err error) { + return nil, fmt.Errorf("The Windows graphdriver does not support Diff()") +} + +// Changes produces a list of changes between the specified layer +// and its parent layer. If parent is "", then all changes will be ADD changes. +func (d *WindowsGraphDriver) Changes(id, parent string) ([]archive.Change, error) { + return nil, fmt.Errorf("The Windows graphdriver does not support Changes()") +} + +// ApplyDiff extracts the changeset from the given diff into the +// layer with the specified id and parent, returning the size of the +// new layer in bytes. +func (d *WindowsGraphDriver) ApplyDiff(id, parent string, diff archive.ArchiveReader) (size int64, err error) { + start := time.Now().UTC() + logrus.Debugf("WindowsGraphDriver ApplyDiff: Start untar layer") + + destination := d.dir(id) + if d.info.Flavour == diffDriver { + destination = filepath.Dir(destination) + } + + if size, err = chrootarchive.ApplyLayer(destination, diff); err != nil { + return + } + logrus.Debugf("WindowsGraphDriver ApplyDiff: Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) + + return +} + +// DiffSize calculates the changes between the specified layer +// and its parent and returns the size in bytes of the changes +// relative to its base filesystem directory. +func (d *WindowsGraphDriver) DiffSize(id, parent string) (size int64, err error) { + changes, err := d.Changes(id, parent) + if err != nil { + return + } + + layerFs, err := d.Get(id, "") + if err != nil { + return + } + defer d.Put(id) + + return archive.ChangesSize(layerFs, changes), nil +} + +func (d *WindowsGraphDriver) CopyDiff(sourceId, id string, parentLayerPaths []string) error { + d.Lock() + defer d.Unlock() + + if d.info.Flavour == filterDriver && d.active[sourceId] == 0 { + if err := hcsshim.ActivateLayer(d.info, sourceId); err != nil { + return err + } + defer func() { + err := hcsshim.DeactivateLayer(d.info, sourceId) + if err != nil { + logrus.Warnf("Failed to Deactivate %s: %s", sourceId, err) + } + }() + } + + return hcsshim.CopyLayer(d.info, sourceId, id, parentLayerPaths) +} + +func (d *WindowsGraphDriver) LayerIdsToPaths(ids []string) []string { + var paths []string + for _, id := range ids { + path, err := d.Get(id, "") + if err != nil { + logrus.Debug("LayerIdsToPaths: Error getting mount path for id", id, ":", err.Error()) + return nil + } + if d.Put(id) != nil { + logrus.Debug("LayerIdsToPaths: Error putting mount path for id", id, ":", err.Error()) + return nil + } + paths = append(paths, path) + } + return paths +} + +func (d *WindowsGraphDriver) GetMetadata(id string) (map[string]string, error) { + return nil, nil +} + +func (d *WindowsGraphDriver) Export(id string, parentLayerPaths []string) (arch archive.Archive, err error) { + layerFs, err := d.Get(id, "") + if err != nil { + return + } + defer func() { + if err != nil { + d.Put(id) + } + }() + + tempFolder := layerFs + "-temp" + if err = os.MkdirAll(tempFolder, 0755); err != nil { + logrus.Errorf("Could not create %s %s", tempFolder, err) + return + } + defer func() { + if err != nil { + if err2 := os.RemoveAll(tempFolder); err2 != nil { + logrus.Warnf("Couldn't clean-up tempFolder: %s %s", tempFolder, err2) + } + } + }() + + if err = hcsshim.ExportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil { + return + } + + archive, err := archive.Tar(tempFolder, archive.Uncompressed) + if err != nil { + return + } + return ioutils.NewReadCloserWrapper(archive, func() error { + err := archive.Close() + d.Put(id) + if err2 := os.RemoveAll(tempFolder); err2 != nil { + logrus.Warnf("Couldn't clean-up tempFolder: %s %s", tempFolder, err2) + } + return err + }), nil + +} + +func (d *WindowsGraphDriver) Import(id string, layerData archive.ArchiveReader, parentLayerPaths []string) (size int64, err error) { + layerFs, err := d.Get(id, "") + if err != nil { + return + } + defer func() { + if err != nil { + d.Put(id) + } + }() + + tempFolder := layerFs + "-temp" + if err = os.MkdirAll(tempFolder, 0755); err != nil { + logrus.Errorf("Could not create %s %s", tempFolder, err) + return + } + defer func() { + if err2 := os.RemoveAll(tempFolder); err2 != nil { + logrus.Warnf("Couldn't clean-up tempFolder: %s %s", tempFolder, err2) + } + }() + + start := time.Now().UTC() + logrus.Debugf("Start untar layer") + if size, err = chrootarchive.ApplyLayer(tempFolder, layerData); err != nil { + return + } + logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) + + if err = hcsshim.ImportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil { + return + } + + return +} diff --git a/daemon/image_delete.go b/daemon/image_delete.go index d0e7e20d4b..05d013191c 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -7,7 +7,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" "github.com/docker/docker/graph" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/utils" @@ -160,7 +159,7 @@ func (daemon *Daemon) canDeleteImage(imgID string, force bool) error { return err } - if err := daemon.graph.WalkHistory(parent, func(p image.Image) error { + if err := daemon.graph.WalkHistory(parent, func(p graph.Image) error { if imgID == p.ID { if container.IsRunning() { if force { diff --git a/daemon/volumes.go b/daemon/volumes.go index f4035992ef..bd1fc9c3b6 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -9,6 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" "github.com/docker/docker/volume/local" @@ -35,7 +36,7 @@ func (m *mountPoint) Setup() (string, error) { if !os.IsNotExist(err) { return "", err } - if err := os.MkdirAll(m.Source, 0755); err != nil { + if err := system.MkdirAll(m.Source, 0755); err != nil { return "", err } } diff --git a/docker/daemon.go b/docker/daemon.go index 5674bb160d..19deb96020 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -18,6 +18,7 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/timeutils" "github.com/docker/docker/pkg/tlsconfig" "github.com/docker/docker/registry" @@ -51,7 +52,7 @@ func migrateKey() (err error) { } }() - if err := os.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil { + if err := system.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil { return fmt.Errorf("Unable to create daemon configuration directory: %s", err) } diff --git a/graph/graph.go b/graph/graph.go index f4d22b1d15..c77de3b963 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -37,6 +37,22 @@ type Graph struct { imageMutex imageMutex // protect images in driver. } +type Image struct { + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + Container string `json:"container,omitempty"` + ContainerConfig runconfig.Config `json:"container_config,omitempty"` + DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` + Config *runconfig.Config `json:"config,omitempty"` + Architecture string `json:"architecture,omitempty"` + OS string `json:"os,omitempty"` + Size int64 + graph Graph +} + var ( // ErrDigestNotSet is used when request the digest for a layer // but the layer has no digest value or content to compute the @@ -79,6 +95,13 @@ func (graph *Graph) restore() error { ids = append(ids, id) } } + + baseIds, err := graph.restoreBaseImages() + if err != nil { + return err + } + ids = append(ids, baseIds...) + graph.idIndex = truncindex.NewTruncIndex(ids) logrus.Debugf("Restored %d elements", len(ids)) return nil @@ -100,7 +123,7 @@ func (graph *Graph) Exists(id string) bool { } // Get returns the image with the given id, or an error if the image doesn't exist. -func (graph *Graph) Get(name string) (*image.Image, error) { +func (graph *Graph) Get(name string) (*Image, error) { id, err := graph.idIndex.Get(name) if err != nil { return nil, fmt.Errorf("could not find image: %v", err) @@ -128,8 +151,8 @@ func (graph *Graph) Get(name string) (*image.Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*image.Image, error) { - img := &image.Image{ +func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*Image, error) { + img := &Image{ ID: stringid.GenerateRandomID(), Comment: comment, Created: time.Now().UTC(), @@ -153,7 +176,7 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, contain } // Register imports a pre-existing image into the graph. -func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) (err error) { +func (graph *Graph) Register(img *Image, layerData archive.ArchiveReader) (err error) { if err := image.ValidateID(img.ID); err != nil { return err } @@ -197,9 +220,10 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) } // Create root filesystem in the driver - if err := graph.driver.Create(img.ID, img.Parent); err != nil { - return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) + if err := createRootFilesystemInDriver(graph, img, layerData); err != nil { + return err } + // Apply the diff/layer if err := graph.storeImage(img, layerData, tmp); err != nil { return err @@ -303,9 +327,9 @@ func (graph *Graph) Delete(name string) error { } // Map returns a list of all images in the graph, addressable by ID. -func (graph *Graph) Map() (map[string]*image.Image, error) { - images := make(map[string]*image.Image) - err := graph.walkAll(func(image *image.Image) { +func (graph *Graph) Map() (map[string]*Image, error) { + images := make(map[string]*Image) + err := graph.walkAll(func(image *Image) { images[image.ID] = image }) if err != nil { @@ -316,7 +340,7 @@ func (graph *Graph) Map() (map[string]*image.Image, error) { // walkAll iterates over each image in the graph, and passes it to a handler. // The walking order is undetermined. -func (graph *Graph) walkAll(handler func(*image.Image)) error { +func (graph *Graph) walkAll(handler func(*Image)) error { files, err := ioutil.ReadDir(graph.root) if err != nil { return err @@ -336,9 +360,9 @@ func (graph *Graph) walkAll(handler func(*image.Image)) error { // If an image of id ID has 3 children images, then the value for key ID // will be a list of 3 images. // If an image has no children, it will not have an entry in the table. -func (graph *Graph) ByParent() (map[string][]*image.Image, error) { - byParent := make(map[string][]*image.Image) - err := graph.walkAll(func(img *image.Image) { +func (graph *Graph) ByParent() (map[string][]*Image, error) { + byParent := make(map[string][]*Image) + err := graph.walkAll(func(img *Image) { parent, err := graph.Get(img.Parent) if err != nil { return @@ -346,7 +370,7 @@ func (graph *Graph) ByParent() (map[string][]*image.Image, error) { if children, exists := byParent[parent.ID]; exists { byParent[parent.ID] = append(children, img) } else { - byParent[parent.ID] = []*image.Image{img} + byParent[parent.ID] = []*Image{img} } }) return byParent, err @@ -354,13 +378,13 @@ func (graph *Graph) ByParent() (map[string][]*image.Image, error) { // Heads returns all heads in the graph, keyed by id. // A head is an image which is not the parent of another image in the graph. -func (graph *Graph) Heads() (map[string]*image.Image, error) { - heads := make(map[string]*image.Image) +func (graph *Graph) Heads() (map[string]*Image, error) { + heads := make(map[string]*Image) byParent, err := graph.ByParent() if err != nil { return nil, err } - err = graph.walkAll(func(image *image.Image) { + err = graph.walkAll(func(image *Image) { // If it's not in the byParent lookup table, then // it's not a parent -> so it's a head! if _, exists := byParent[image.ID]; !exists { @@ -374,33 +398,8 @@ func (graph *Graph) imageRoot(id string) string { return filepath.Join(graph.root, id) } -// storeImage stores file system layer data for the given image to the -// graph's storage driver. Image metadata is stored in a file -// at the specified root directory. -func (graph *Graph) storeImage(img *image.Image, layerData archive.ArchiveReader, root string) (err error) { - // Store the layer. If layerData is not nil, unpack it into the new layer - if layerData != nil { - if img.Size, err = graph.driver.ApplyDiff(img.ID, img.Parent, layerData); err != nil { - return err - } - } - - if err := graph.saveSize(root, int(img.Size)); err != nil { - return err - } - - f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600)) - if err != nil { - return err - } - - defer f.Close() - - return json.NewEncoder(f).Encode(img) -} - // loadImage fetches the image with the given id from the graph. -func (graph *Graph) loadImage(id string) (*image.Image, error) { +func (graph *Graph) loadImage(id string) (*Image, error) { root := graph.imageRoot(id) // Open the JSON file to decode by streaming @@ -410,7 +409,7 @@ func (graph *Graph) loadImage(id string) (*image.Image, error) { } defer jsonSource.Close() - img := &image.Image{} + img := &Image{} dec := json.NewDecoder(jsonSource) // Decode the JSON data @@ -488,7 +487,13 @@ func jsonPath(root string) string { return filepath.Join(root, "json") } -// TarLayer returns a tar archive of the image's filesystem layer. -func (graph *Graph) TarLayer(img *image.Image) (arch archive.Archive, err error) { - return graph.driver.Diff(img.ID, img.Parent) +// Build an Image object from raw json data +func NewImgJSON(src []byte) (*Image, error) { + ret := &Image{} + + // FIXME: Is there a cleaner way to "purify" the input json? + if err := json.Unmarshal(src, ret); err != nil { + return nil, err + } + return ret, nil } diff --git a/graph/graph_test.go b/graph/graph_test.go index 71c84b0894..946ac0652c 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -68,7 +68,7 @@ func TestInterruptedRegister(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data - image := &image.Image{ + image := &Image{ ID: stringid.GenerateRandomID(), Comment: "testing", Created: time.Now(), @@ -128,7 +128,7 @@ func TestRegister(t *testing.T) { if err != nil { t.Fatal(err) } - image := &image.Image{ + image := &Image{ ID: stringid.GenerateRandomID(), Comment: "testing", Created: time.Now(), @@ -232,19 +232,19 @@ func TestByParent(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) - parentImage := &image.Image{ + parentImage := &Image{ ID: stringid.GenerateRandomID(), Comment: "parent", Created: time.Now(), Parent: "", } - childImage1 := &image.Image{ + childImage1 := &Image{ ID: stringid.GenerateRandomID(), Comment: "child1", Created: time.Now(), Parent: parentImage.ID, } - childImage2 := &image.Image{ + childImage2 := &Image{ ID: stringid.GenerateRandomID(), Comment: "child2", Created: time.Now(), @@ -264,7 +264,7 @@ func TestByParent(t *testing.T) { } } -func createTestImage(graph *Graph, t *testing.T) *image.Image { +func createTestImage(graph *Graph, t *testing.T) *Image { archive, err := fakeTar() if err != nil { t.Fatal(err) diff --git a/graph/graph_unix.go b/graph/graph_unix.go new file mode 100644 index 0000000000..8542767089 --- /dev/null +++ b/graph/graph_unix.go @@ -0,0 +1,114 @@ +// +build !windows + +package graph + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/system" +) + +// setupInitLayer populates a directory with mountpoints suitable +// for bind-mounting dockerinit into the container. The mountpoint is simply an +// empty file at /.dockerinit +// +// This extra layer is used by all containers as the top-most ro layer. It protects +// the container from unwanted side-effects on the rw layer. +func SetupInitLayer(initLayer string) error { + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/.dockerenv": "file", + "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", + "/dev/console": "file", + "/etc/mtab": "/proc/mounts", + } { + parts := strings.Split(pth, "/") + prev := "/" + for _, p := range parts[1:] { + prev = filepath.Join(prev, p) + syscall.Unlink(filepath.Join(initLayer, prev)) + } + + if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil { + if os.IsNotExist(err) { + if err := system.MkdirAll(filepath.Join(initLayer, filepath.Dir(pth)), 0755); err != nil { + return err + } + switch typ { + case "dir": + if err := system.MkdirAll(filepath.Join(initLayer, pth), 0755); err != nil { + return err + } + case "file": + f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755) + if err != nil { + return err + } + f.Close() + default: + if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil { + return err + } + } + } else { + return err + } + } + } + + // Layer is ready to use, if it wasn't before. + return nil +} + +func createRootFilesystemInDriver(graph *Graph, img *Image, layerData archive.ArchiveReader) error { + if err := graph.driver.Create(img.ID, img.Parent); err != nil { + return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) + } + return nil +} + +func (graph *Graph) restoreBaseImages() ([]string, error) { + return nil, nil +} + +// storeImage stores file system layer data for the given image to the +// graph's storage driver. Image metadata is stored in a file +// at the specified root directory. +func (graph *Graph) storeImage(img *Image, layerData archive.ArchiveReader, root string) (err error) { + // Store the layer. If layerData is not nil, unpack it into the new layer + if layerData != nil { + if img.Size, err = graph.driver.ApplyDiff(img.ID, img.Parent, layerData); err != nil { + return err + } + } + + if err := graph.saveSize(root, int(img.Size)); err != nil { + return err + } + + f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600)) + if err != nil { + return err + } + + defer f.Close() + + return json.NewEncoder(f).Encode(img) +} + +// TarLayer returns a tar archive of the image's filesystem layer. +func (graph *Graph) TarLayer(img *Image) (arch archive.Archive, err error) { + return graph.driver.Diff(img.ID, img.Parent) +} diff --git a/graph/graph_windows.go b/graph/graph_windows.go new file mode 100644 index 0000000000..afa482ad10 --- /dev/null +++ b/graph/graph_windows.go @@ -0,0 +1,160 @@ +// +build windows + +package graph + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/daemon/graphdriver/windows" + "github.com/docker/docker/pkg/archive" +) + +// setupInitLayer populates a directory with mountpoints suitable +// for bind-mounting dockerinit into the container. T +func SetupInitLayer(initLayer string) error { + return nil +} + +func createRootFilesystemInDriver(graph *Graph, img *Image, layerData archive.ArchiveReader) error { + if wd, ok := graph.driver.(*windows.WindowsGraphDriver); ok { + if img.Container != "" && layerData == nil { + logrus.Debugf("Copying from container %s.", img.Container) + + var ids []string + if img.Parent != "" { + parentImg, err := graph.Get(img.Parent) + if err != nil { + return err + } + + ids, err = graph.ParentLayerIds(parentImg) + if err != nil { + return err + } + } + + if err := wd.CopyDiff(img.Container, img.ID, wd.LayerIdsToPaths(ids)); err != nil { + return fmt.Errorf("Driver %s failed to copy image rootfs %s: %s", graph.driver, img.Container, err) + } + } else if img.Parent == "" { + if err := graph.driver.Create(img.ID, img.Parent); err != nil { + return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) + } + } + } else { + // This fallback allows the use of VFS during daemon development. + if err := graph.driver.Create(img.ID, img.Parent); err != nil { + return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) + } + } + return nil +} + +func (graph *Graph) restoreBaseImages() ([]string, error) { + // TODO Windows. This needs implementing (@swernli) + return nil, nil +} + +// ParentLayerIds returns a list of all parent image IDs for the given image. +func (graph *Graph) ParentLayerIds(img *Image) (ids []string, err error) { + for i := img; i != nil && err == nil; i, err = graph.GetParent(i) { + ids = append(ids, i.ID) + } + + return +} + +// storeImage stores file system layer data for the given image to the +// graph's storage driver. Image metadata is stored in a file +// at the specified root directory. +func (graph *Graph) storeImage(img *Image, layerData archive.ArchiveReader, root string) (err error) { + + if wd, ok := graph.driver.(*windows.WindowsGraphDriver); ok { + // Store the layer. If layerData is not nil and this isn't a base image, + // unpack it into the new layer + if layerData != nil && img.Parent != "" { + var ids []string + if img.Parent != "" { + parentImg, err := graph.Get(img.Parent) + if err != nil { + return err + } + + ids, err = graph.ParentLayerIds(parentImg) + if err != nil { + return err + } + } + + if img.Size, err = wd.Import(img.ID, layerData, wd.LayerIdsToPaths(ids)); err != nil { + return err + } + } + + if err := graph.saveSize(root, int(img.Size)); err != nil { + return err + } + + f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600)) + if err != nil { + return err + } + + defer f.Close() + + return json.NewEncoder(f).Encode(img) + } else { + // We keep this functionality here so that we can still work with the + // VFS driver during development. This will not be used for actual running + // of Windows containers. Without this code, it would not be possible to + // docker pull using the VFS driver. + + // Store the layer. If layerData is not nil, unpack it into the new layer + if layerData != nil { + if img.Size, err = graph.driver.ApplyDiff(img.ID, img.Parent, layerData); err != nil { + return err + } + } + + if err := graph.saveSize(root, int(img.Size)); err != nil { + return err + } + + f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600)) + if err != nil { + return err + } + + defer f.Close() + + return json.NewEncoder(f).Encode(img) + } +} + +// TarLayer returns a tar archive of the image's filesystem layer. +func (graph *Graph) TarLayer(img *Image) (arch archive.Archive, err error) { + if wd, ok := graph.driver.(*windows.WindowsGraphDriver); ok { + var ids []string + if img.Parent != "" { + parentImg, err := graph.Get(img.Parent) + if err != nil { + return nil, err + } + + ids, err = graph.ParentLayerIds(parentImg) + if err != nil { + return nil, err + } + } + + return wd.Export(img.ID, wd.LayerIdsToPaths(ids)) + } else { + // We keep this functionality here so that we can still work with the VFS + // driver during development. VFS is not supported (and just will not work) + // for Windows containers. + return graph.driver.Diff(img.ID, img.Parent) + } +} diff --git a/graph/history.go b/graph/history.go index 4bb93fc3f9..f92ebf3b03 100644 --- a/graph/history.go +++ b/graph/history.go @@ -5,13 +5,12 @@ import ( "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/image" "github.com/docker/docker/utils" ) // WalkHistory calls the handler function for each image in the // provided images lineage starting from immediate parent. -func (graph *Graph) WalkHistory(img *image.Image, handler func(image.Image) error) (err error) { +func (graph *Graph) WalkHistory(img *Image, handler func(Image) error) (err error) { currentImg := img for currentImg != nil { if handler != nil { @@ -29,7 +28,7 @@ func (graph *Graph) WalkHistory(img *image.Image, handler func(image.Image) erro // depth returns the number of parents for a // current image -func (graph *Graph) depth(img *image.Image) (int, error) { +func (graph *Graph) depth(img *Image) (int, error) { var ( count = 0 parent = img @@ -54,7 +53,7 @@ const MaxImageDepth = 127 // CheckDepth returns an error if the depth of an image, as returned // by ImageDepth, is too large to support creating a container from it // on this daemon. -func (graph *Graph) CheckDepth(img *image.Image) error { +func (graph *Graph) CheckDepth(img *Image) error { // We add 2 layers to the depth because the container's rw and // init layer add to the restriction depth, err := graph.depth(img) @@ -86,7 +85,7 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { history := []*types.ImageHistory{} - err = s.graph.WalkHistory(foundImage, func(img image.Image) error { + err = s.graph.WalkHistory(foundImage, func(img Image) error { history = append(history, &types.ImageHistory{ ID: img.ID, Created: img.Created.Unix(), @@ -101,14 +100,14 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { return history, err } -func (graph *Graph) GetParent(img *image.Image) (*image.Image, error) { +func (graph *Graph) GetParent(img *Image) (*Image, error) { if img.Parent == "" { return nil, nil } return graph.Get(img.Parent) } -func (graph *Graph) GetParentsSize(img *image.Image, size int64) int64 { +func (graph *Graph) GetParentsSize(img *Image, size int64) int64 { parentImage, err := graph.GetParent(img) if err != nil || parentImage == nil { return size diff --git a/graph/list.go b/graph/list.go index fd4d2e89fe..7c6027b4bf 100644 --- a/graph/list.go +++ b/graph/list.go @@ -8,7 +8,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/utils" ) @@ -32,7 +31,7 @@ func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } func (s *TagStore) Images(config *ImagesConfig) ([]*types.Image, error) { var ( - allImages map[string]*image.Image + allImages map[string]*Image err error filtTagged = true filtLabel = false diff --git a/graph/load.go b/graph/load.go index 9afde34c9a..3a4a2746fc 100644 --- a/graph/load.go +++ b/graph/load.go @@ -98,7 +98,7 @@ func (s *TagStore) recursiveLoad(address, tmpImageDir string) error { logrus.Debugf("Error reading embedded tar", err) return err } - img, err := image.NewImgJSON(imageJson) + img, err := NewImgJSON(imageJson) if err != nil { logrus.Debugf("Error unmarshalling json", err) return err diff --git a/graph/manifest_test.go b/graph/manifest_test.go index 6b1a608073..f686494672 100644 --- a/graph/manifest_test.go +++ b/graph/manifest_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/docker/distribution/digest" - "github.com/docker/docker/image" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" @@ -124,7 +123,7 @@ func TestManifestTarsumCache(t *testing.T) { if err != nil { t.Fatal(err) } - img := &image.Image{ID: testManifestImageID} + img := &Image{ID: testManifestImageID} if err := store.graph.Register(img, archive); err != nil { t.Fatal(err) } @@ -190,7 +189,7 @@ func TestManifestDigestCheck(t *testing.T) { if err != nil { t.Fatal(err) } - img := &image.Image{ID: testManifestImageID} + img := &Image{ID: testManifestImageID} if err := store.graph.Register(img, archive); err != nil { t.Fatal(err) } diff --git a/graph/pull.go b/graph/pull.go index b4e7af6707..0f04c8bff1 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -13,7 +13,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/cliconfig" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" @@ -387,7 +386,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint imgJSON []byte imgSize int err error - img *image.Image + img *Image ) retries := 5 for j := 1; j <= retries; j++ { @@ -399,7 +398,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint time.Sleep(time.Duration(j) * 500 * time.Millisecond) continue } - img, err = image.NewImgJSON(imgJSON) + img, err = NewImgJSON(imgJSON) layersDownloaded = true if err != nil && j == retries { out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) @@ -540,7 +539,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis // downloadInfo is used to pass information from download to extractor type downloadInfo struct { imgJSON []byte - img *image.Image + img *Image digest digest.Digest tmpFile *os.File length int64 @@ -556,7 +555,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis imgJSON = []byte(manifest.History[i].V1Compatibility) ) - img, err := image.NewImgJSON(imgJSON) + img, err := NewImgJSON(imgJSON) if err != nil { return false, fmt.Errorf("failed to parse json: %s", err) } diff --git a/graph/push.go b/graph/push.go index 82615ee0eb..628bb3157a 100644 --- a/graph/push.go +++ b/graph/push.go @@ -11,7 +11,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/cliconfig" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -349,7 +348,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o } layersSeen := make(map[string]bool) - layers := []*image.Image{} + layers := []*Image{} for ; layer != nil; layer, err = s.graph.GetParent(layer) { if err != nil { return err @@ -445,7 +444,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o } // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk -func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *streamformatter.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (digest.Digest, error) { +func (s *TagStore) pushV2Image(r *registry.Session, img *Image, endpoint *registry.Endpoint, imageName string, sf *streamformatter.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (digest.Digest, error) { out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) image, err := s.graph.Get(img.ID) diff --git a/graph/service.go b/graph/service.go index 7e4bb382c5..4a8c3d3b8f 100644 --- a/graph/service.go +++ b/graph/service.go @@ -3,6 +3,7 @@ package graph import ( "fmt" "io" + "runtime" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" @@ -58,17 +59,21 @@ func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { // ImageTarLayer return the tarLayer of the image func (s *TagStore) ImageTarLayer(name string, dest io.Writer) error { if image, err := s.LookupImage(name); err == nil && image != nil { - fs, err := s.graph.TarLayer(image) - if err != nil { - return err - } - defer fs.Close() + // On Windows, the base layer cannot be exported + if runtime.GOOS != "windows" || image.Parent != "" { - written, err := io.Copy(dest, fs) - if err != nil { - return err + fs, err := s.graph.TarLayer(image) + if err != nil { + return err + } + defer fs.Close() + + written, err := io.Copy(dest, fs) + if err != nil { + return err + } + logrus.Debugf("rendered layer for %s of [%d] size", image.ID, written) } - logrus.Debugf("rendered layer for %s of [%d] size", image.ID, written) return nil } return fmt.Errorf("No such image: %s", name) diff --git a/graph/tags.go b/graph/tags.go index 166a3d733f..0b7eff891e 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/daemon/events" "github.com/docker/docker/graph/tags" - "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" @@ -126,7 +125,7 @@ func (store *TagStore) reload() error { return nil } -func (store *TagStore) LookupImage(name string) (*image.Image, error) { +func (store *TagStore) LookupImage(name string) (*Image, error) { // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else // (so we can pass all errors here) repoName, ref := parsers.ParseRepositoryTag(name) @@ -135,7 +134,7 @@ func (store *TagStore) LookupImage(name string) (*image.Image, error) { } var ( err error - img *image.Image + img *Image ) img, err = store.GetImage(repoName, ref) @@ -330,7 +329,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) { return nil, nil } -func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) { +func (store *TagStore) GetImage(repoName, refOrID string) (*Image, error) { repo, err := store.Get(repoName) if err != nil { diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index ad919d9bb8..ca52449bd6 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -11,7 +11,6 @@ import ( "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/graphdriver" _ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests - "github.com/docker/docker/image" "github.com/docker/docker/trust" "github.com/docker/docker/utils" ) @@ -80,7 +79,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - img := &image.Image{ID: testOfficialImageID} + img := &Image{ID: testOfficialImageID} if err := graph.Register(img, officialArchive); err != nil { t.Fatal(err) } @@ -91,7 +90,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - img = &image.Image{ID: testPrivateImageID} + img = &Image{ID: testPrivateImageID} if err := graph.Register(img, privateArchive); err != nil { t.Fatal(err) } diff --git a/image/image.go b/image/image.go index 218f18f2d7..1fe4357c2b 100644 --- a/image/image.go +++ b/image/image.go @@ -1,42 +1,12 @@ package image import ( - "encoding/json" "fmt" "regexp" - "time" - - "github.com/docker/docker/runconfig" ) var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) -type Image struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - Container string `json:"container,omitempty"` - ContainerConfig runconfig.Config `json:"container_config,omitempty"` - DockerVersion string `json:"docker_version,omitempty"` - Author string `json:"author,omitempty"` - Config *runconfig.Config `json:"config,omitempty"` - Architecture string `json:"architecture,omitempty"` - OS string `json:"os,omitempty"` - Size int64 -} - -// Build an Image object from raw json data -func NewImgJSON(src []byte) (*Image, error) { - ret := &Image{} - - // FIXME: Is there a cleaner way to "purify" the input json? - if err := json.Unmarshal(src, ret); err != nil { - return nil, err - } - return ret, nil -} - // Check wheather id is a valid image ID or not func ValidateID(id string) error { if ok := validHex.MatchString(id); !ok {