Explorar o código

Adds ability to squash image after build

Allow built images to be squash to scratch.
Squashing does not destroy any images or layers, and preserves the
build cache.

Introduce a new CLI argument --squash to docker build
Introduce a new param to the build API endpoint `squash`

Once the build is complete, docker creates a new image loading the diffs
from each layer into a single new layer and references all the parent's
layers.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff %!s(int64=9) %!d(string=hai) anos
pai
achega
362369b4bb

+ 1 - 0
api/server/router/build/build_routes.go

@@ -54,6 +54,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 	options.NetworkMode = r.FormValue("networkmode")
 	options.Tags = r.Form["t"]
 	options.SecurityOpt = r.Form["securityopt"]
+	options.Squash = httputils.BoolValue(r, "squash")
 
 	if r.Form.Get("shmsize") != "" {
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)

+ 7 - 1
builder/builder.go

@@ -135,9 +135,15 @@ type Backend interface {
 	// TODO: make an Extract method instead of passing `decompress`
 	// TODO: do not pass a FileInfo, instead refactor the archive package to export a Walk function that can be used
 	// with Context.Walk
-	//ContainerCopy(name string, res string) (io.ReadCloser, error)
+	// ContainerCopy(name string, res string) (io.ReadCloser, error)
 	// TODO: use copyBackend api
 	CopyOnBuild(containerID string, destPath string, src FileInfo, decompress bool) error
+
+	// HasExperimental checks if the backend supports experimental features
+	HasExperimental() bool
+
+	// SquashImage squashes the fs layers from the provided image down to the specified `to` image
+	SquashImage(from string, to string) (string, error)
 }
 
 // Image represents a Docker image used by the builder.

+ 18 - 0
builder/dockerfile/builder.go

@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/Sirupsen/logrus"
+	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/container"
@@ -18,6 +19,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/reference"
+	perrors "github.com/pkg/errors"
 	"golang.org/x/net/context"
 )
 
@@ -77,6 +79,7 @@ type Builder struct {
 	id string
 
 	imageCache builder.ImageCache
+	from       builder.Image
 }
 
 // BuildManager implements builder.Backend and is shared across all Builder objects.
@@ -91,6 +94,9 @@ func NewBuildManager(b builder.Backend) (bm *BuildManager) {
 
 // BuildFromContext builds a new image from a given context.
 func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) {
+	if buildOptions.Squash && !bm.backend.HasExperimental() {
+		return "", apierrors.NewBadRequestError(errors.New("squash is only supported with experimental mode"))
+	}
 	buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(src, remote, pg.ProgressReaderFunc)
 	if err != nil {
 		return "", err
@@ -100,6 +106,7 @@ func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser,
 			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
 		}
 	}()
+
 	if len(dockerfileName) > 0 {
 		buildOptions.Dockerfile = dockerfileName
 	}
@@ -286,6 +293,17 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
 		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
 	}
 
+	if b.options.Squash {
+		var fromID string
+		if b.from != nil {
+			fromID = b.from.ImageID()
+		}
+		b.image, err = b.docker.SquashImage(b.image, fromID)
+		if err != nil {
+			return "", perrors.Wrap(err, "error squashing image")
+		}
+	}
+
 	imageID := image.ID(b.image)
 	for _, rt := range repoAndTags {
 		if err := b.docker.TagImageWithReference(imageID, rt); err != nil {

+ 1 - 0
builder/dockerfile/dispatchers.go

@@ -221,6 +221,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
 			}
 		}
 	}
+	b.from = image
 
 	return b.processImageFrom(image)
 }

+ 6 - 0
cli/command/image/build.go

@@ -59,6 +59,7 @@ type buildOptions struct {
 	compress       bool
 	securityOpt    []string
 	networkMode    string
+	squash         bool
 }
 
 // NewBuildCommand creates a new `docker build` command
@@ -110,6 +111,10 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
 
 	command.AddTrustedFlags(flags, true)
 
+	if dockerCli.HasExperimental() {
+		flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
+	}
+
 	return cmd
 }
 
@@ -305,6 +310,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
 		CacheFrom:      options.cacheFrom,
 		SecurityOpt:    options.securityOpt,
 		NetworkMode:    options.networkMode,
+		Squash:         options.squash,
 	}
 
 	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)

+ 30 - 3
daemon/graphdriver/aufs/aufs.go

@@ -74,6 +74,7 @@ type Driver struct {
 	ctr           *graphdriver.RefCounter
 	pathCacheLock sync.Mutex
 	pathCache     map[string]string
+	naiveDiff     graphdriver.DiffDriver
 }
 
 // Init returns a new AUFS driver.
@@ -137,6 +138,8 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 			return nil, err
 		}
 	}
+
+	a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, uidMaps, gidMaps)
 	return a, nil
 }
 
@@ -225,7 +228,7 @@ func (a *Driver) Create(id, parent, mountLabel string, storageOpt map[string]str
 	defer f.Close()
 
 	if parent != "" {
-		ids, err := getParentIds(a.rootPath(), parent)
+		ids, err := getParentIDs(a.rootPath(), parent)
 		if err != nil {
 			return err
 		}
@@ -427,9 +430,22 @@ func (a *Driver) Put(id string) error {
 	return err
 }
 
+// isParent returns if the passed in parent is the direct parent of the passed in layer
+func (a *Driver) isParent(id, parent string) bool {
+	parents, _ := getParentIDs(a.rootPath(), id)
+	if parent == "" && len(parents) > 0 {
+		return false
+	}
+	return !(len(parents) > 0 && parent != parents[0])
+}
+
 // Diff produces an archive of the changes between the specified
 // layer and its parent layer which may be "".
 func (a *Driver) Diff(id, parent string) (io.ReadCloser, error) {
+	if !a.isParent(id, parent) {
+		return a.naiveDiff.Diff(id, parent)
+	}
+
 	// AUFS doesn't need the parent layer to produce a diff.
 	return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
 		Compression:     archive.Uncompressed,
@@ -465,6 +481,9 @@ func (a *Driver) applyDiff(id string, diff io.Reader) error {
 // and its parent and returns the size in bytes of the changes
 // relative to its base filesystem directory.
 func (a *Driver) DiffSize(id, parent string) (size int64, err error) {
+	if !a.isParent(id, parent) {
+		return a.naiveDiff.DiffSize(id, parent)
+	}
 	// AUFS doesn't need the parent layer to calculate the diff size.
 	return directory.Size(path.Join(a.rootPath(), "diff", id))
 }
@@ -473,7 +492,11 @@ func (a *Driver) DiffSize(id, parent string) (size int64, err error) {
 // layer with the specified id and parent, returning the size of the
 // new layer in bytes.
 func (a *Driver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) {
-	// AUFS doesn't need the parent id to apply the diff.
+	if !a.isParent(id, parent) {
+		return a.naiveDiff.ApplyDiff(id, parent, diff)
+	}
+
+	// AUFS doesn't need the parent id to apply the diff if it is the direct parent.
 	if err = a.applyDiff(id, diff); err != nil {
 		return
 	}
@@ -484,6 +507,10 @@ func (a *Driver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err e
 // 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 (a *Driver) Changes(id, parent string) ([]archive.Change, error) {
+	if !a.isParent(id, parent) {
+		return a.naiveDiff.Changes(id, parent)
+	}
+
 	// AUFS doesn't have snapshots, so we need to get changes from all parent
 	// layers.
 	layers, err := a.getParentLayerPaths(id)
@@ -494,7 +521,7 @@ func (a *Driver) Changes(id, parent string) ([]archive.Change, error) {
 }
 
 func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
-	parentIds, err := getParentIds(a.rootPath(), id)
+	parentIds, err := getParentIDs(a.rootPath(), id)
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
daemon/graphdriver/aufs/aufs_test.go

@@ -424,7 +424,7 @@ func TestChanges(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	changes, err = d.Changes("3", "")
+	changes, err = d.Changes("3", "2")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -530,7 +530,7 @@ func TestChildDiffSize(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	diffSize, err = d.DiffSize("2", "")
+	diffSize, err = d.DiffSize("2", "1")
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 1
daemon/graphdriver/aufs/dirs.go

@@ -29,7 +29,7 @@ func loadIds(root string) ([]string, error) {
 //
 // If there are no lines in the file then the id has no parent
 // and an empty slice is returned.
-func getParentIds(root, id string) ([]string, error) {
+func getParentIDs(root, id string) ([]string, error) {
 	f, err := os.Open(path.Join(root, "layers", id))
 	if err != nil {
 		return nil, err

+ 8 - 3
daemon/graphdriver/driver.go

@@ -78,9 +78,8 @@ type ProtoDriver interface {
 	Cleanup() error
 }
 
-// Driver is the interface for layered/snapshot file system drivers.
-type Driver interface {
-	ProtoDriver
+// DiffDriver is the interface to use to implement graph diffs
+type DiffDriver interface {
 	// Diff produces an archive of the changes between the specified
 	// layer and its parent layer which may be "".
 	Diff(id, parent string) (io.ReadCloser, error)
@@ -98,6 +97,12 @@ type Driver interface {
 	DiffSize(id, parent string) (size int64, err error)
 }
 
+// Driver is the interface for layered/snapshot file system drivers.
+type Driver interface {
+	ProtoDriver
+	DiffDriver
+}
+
 // DiffGetterDriver is the interface for layered file system drivers that
 // provide a specialized function for getting file contents for tar-split.
 type DiffGetterDriver interface {

+ 47 - 8
daemon/graphdriver/overlay2/overlay.go

@@ -11,6 +11,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"syscall"
@@ -44,7 +45,7 @@ var (
 
 // Each container/image has at least a "diff" directory and "link" file.
 // If there is also a "lower" file when there are diff layers
-// below  as well as "merged" and "work" directories. The "diff" directory
+// below as well as "merged" and "work" directories. The "diff" directory
 // has the upper layer of the overlay and is used to capture any
 // changes to the layer. The "lower" file contains all the lower layer
 // mounts separated by ":" and ordered from uppermost to lowermost
@@ -86,12 +87,13 @@ type overlayOptions struct {
 
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
 type Driver struct {
-	home     string
-	uidMaps  []idtools.IDMap
-	gidMaps  []idtools.IDMap
-	ctr      *graphdriver.RefCounter
-	quotaCtl *quota.Control
-	options  overlayOptions
+	home      string
+	uidMaps   []idtools.IDMap
+	gidMaps   []idtools.IDMap
+	ctr       *graphdriver.RefCounter
+	quotaCtl  *quota.Control
+	options   overlayOptions
+	naiveDiff graphdriver.DiffDriver
 }
 
 var (
@@ -163,6 +165,8 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
 	}
 
+	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
+
 	if backingFs == "xfs" {
 		// Try to enable project quota support over xfs.
 		if d.quotaCtl, err = quota.NewControl(home); err == nil {
@@ -525,7 +529,7 @@ func (d *Driver) Put(id string) error {
 		return nil
 	}
 	if err := syscall.Unmount(mountpoint, 0); err != nil {
-		logrus.Debugf("Failed to unmount %s overlay: %v", id, err)
+		logrus.Debugf("Failed to unmount %s overlay: %s - %v", id, mountpoint, err)
 	}
 	return nil
 }
@@ -536,8 +540,33 @@ func (d *Driver) Exists(id string) bool {
 	return err == nil
 }
 
+// isParent returns if the passed in parent is the direct parent of the passed in layer
+func (d *Driver) isParent(id, parent string) bool {
+	lowers, err := d.getLowerDirs(id)
+	if err != nil {
+		return false
+	}
+	if parent == "" && len(lowers) > 0 {
+		return false
+	}
+
+	parentDir := d.dir(parent)
+	var ld string
+	if len(lowers) > 0 {
+		ld = filepath.Dir(lowers[0])
+	}
+	if ld == "" && parent == "" {
+		return true
+	}
+	return ld == parentDir
+}
+
 // ApplyDiff applies the new layer into a root
 func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64, err error) {
+	if !d.isParent(id, parent) {
+		return d.naiveDiff.ApplyDiff(id, parent, diff)
+	}
+
 	applyDir := d.getDiffPath(id)
 
 	logrus.Debugf("Applying tar in %s", applyDir)
@@ -563,12 +592,19 @@ func (d *Driver) getDiffPath(id string) string {
 // and its parent and returns the size in bytes of the changes
 // relative to its base filesystem directory.
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
+	if !d.isParent(id, parent) {
+		return d.naiveDiff.DiffSize(id, parent)
+	}
 	return directory.Size(d.getDiffPath(id))
 }
 
 // Diff produces an archive of the changes between the specified
 // layer and its parent layer which may be "".
 func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
+	if !d.isParent(id, parent) {
+		return d.naiveDiff.Diff(id, parent)
+	}
+
 	diffPath := d.getDiffPath(id)
 	logrus.Debugf("Tar with options on %s", diffPath)
 	return archive.TarWithOptions(diffPath, &archive.TarOptions{
@@ -582,6 +618,9 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
 // 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 *Driver) Changes(id, parent string) ([]archive.Change, error) {
+	if !d.isParent(id, parent) {
+		return d.naiveDiff.Changes(id, parent)
+	}
 	// Overlay doesn't have snapshots, so we need to get changes from all parent
 	// layers.
 	diffPath := d.getDiffPath(id)

+ 87 - 0
daemon/images.go

@@ -1,9 +1,13 @@
 package daemon
 
 import (
+	"encoding/json"
 	"fmt"
 	"path"
 	"sort"
+	"time"
+
+	"github.com/pkg/errors"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
@@ -241,6 +245,89 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool, withExtraAttrs
 	return images, nil
 }
 
+// SquashImage creates a new image with the diff of the specified image and the specified parent.
+// This new image contains only the layers from it's parent + 1 extra layer which contains the diff of all the layers in between.
+// The existing image(s) is not destroyed.
+// If no parent is specified, a new image with the diff of all the specified image's layers merged into a new layer that has no parents.
+func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
+	img, err := daemon.imageStore.Get(image.ID(id))
+	if err != nil {
+		return "", err
+	}
+
+	var parentImg *image.Image
+	var parentChainID layer.ChainID
+	if len(parent) != 0 {
+		parentImg, err = daemon.imageStore.Get(image.ID(parent))
+		if err != nil {
+			return "", errors.Wrap(err, "error getting specified parent layer")
+		}
+		parentChainID = parentImg.RootFS.ChainID()
+	} else {
+		rootFS := image.NewRootFS()
+		parentImg = &image.Image{RootFS: rootFS}
+	}
+
+	l, err := daemon.layerStore.Get(img.RootFS.ChainID())
+	if err != nil {
+		return "", errors.Wrap(err, "error getting image layer")
+	}
+	defer daemon.layerStore.Release(l)
+
+	ts, err := l.TarStreamFrom(parentChainID)
+	if err != nil {
+		return "", errors.Wrapf(err, "error getting tar stream to parent")
+	}
+	defer ts.Close()
+
+	newL, err := daemon.layerStore.Register(ts, parentChainID)
+	if err != nil {
+		return "", errors.Wrap(err, "error registering layer")
+	}
+	defer daemon.layerStore.Release(newL)
+
+	var newImage image.Image
+	newImage = *img
+	newImage.RootFS = nil
+
+	var rootFS image.RootFS
+	rootFS = *parentImg.RootFS
+	rootFS.DiffIDs = append(rootFS.DiffIDs, newL.DiffID())
+	newImage.RootFS = &rootFS
+
+	for i, hi := range newImage.History {
+		if i >= len(parentImg.History) {
+			hi.EmptyLayer = true
+		}
+		newImage.History[i] = hi
+	}
+
+	now := time.Now()
+	var historyComment string
+	if len(parent) > 0 {
+		historyComment = fmt.Sprintf("merge %s to %s", id, parent)
+	} else {
+		historyComment = fmt.Sprintf("create new from %s", id)
+	}
+
+	newImage.History = append(newImage.History, image.History{
+		Created: now,
+		Comment: historyComment,
+	})
+	newImage.Created = now
+
+	b, err := json.Marshal(&newImage)
+	if err != nil {
+		return "", errors.Wrap(err, "error marshalling image config")
+	}
+
+	newImgID, err := daemon.imageStore.Create(b)
+	if err != nil {
+		return "", errors.Wrap(err, "error creating new image after squash")
+	}
+	return string(newImgID), nil
+}
+
 func newImage(image *image.Image, virtualSize int64) *types.ImageSummary {
 	newImage := new(types.ImageSummary)
 	newImage.ParentID = image.Parent.String()

+ 5 - 0
distribution/xfer/download_test.go

@@ -3,6 +3,7 @@ package xfer
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"runtime"
@@ -31,6 +32,10 @@ func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
 	return ioutil.NopCloser(bytes.NewBuffer(ml.layerData.Bytes())), nil
 }
 
+func (ml *mockLayer) TarStreamFrom(layer.ChainID) (io.ReadCloser, error) {
+	return nil, fmt.Errorf("not implemented")
+}
+
 func (ml *mockLayer) ChainID() layer.ChainID {
 	return ml.chainID
 }

+ 1 - 0
docs/reference/api/docker_remote_api_v1.25.md

@@ -1800,6 +1800,7 @@ or being killed.
         variable expansion in other Dockerfile instructions. This is not meant for
         passing secret values. [Read more about the buildargs instruction](../../reference/builder.md#arg)
 -   **shmsize** - Size of `/dev/shm` in bytes. The size must be greater than 0.  If omitted the system uses 64MB.
+-   **squash** - squash the resulting images layers into a single layer (boolean) **Experimental Only**
 -   **labels** – JSON map of string pairs for labels to set on the image.
 -   **networkmode** - Sets the networking mode for the run commands during
         build. Supported standard values are: `bridge`, `host`, `none`, and

+ 18 - 0
docs/reference/commandline/build.md

@@ -54,6 +54,7 @@ Options:
                                 The format is `<number><unit>`. `number` must be greater than `0`.
                                 Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes),
                                 or `g` (gigabytes). If you omit the unit, the system uses bytes.
+  --squash                      Squash newly built layers into a single new layer (**Experimental Only**) 
   -t, --tag value               Name and optionally a tag in the 'name:tag' format (default [])
       --ulimit value            Ulimit options (default [])
 ```
@@ -432,3 +433,20 @@ Linux namespaces. On Microsoft Windows, you can specify these values:
 | `hyperv`  | Hyper-V hypervisor partition-based isolation.                                                                                                                 |
 
 Specifying the `--isolation` flag without a value is the same as setting `--isolation="default"`.
+
+
+### Squash an image's layers (--squash) **Experimental Only**
+
+Once the image is built, squash the new layers into a new image with a single
+new layer. Squashing does not destroy any existing image, rather it creates a new
+image with the content of the squshed layers. This effectively makes it look
+like all `Dockerfile` commands were created with a single layer. The build
+cache is preserved with this method.
+
+**Note**: using this option means the new image will not be able to take
+advantage of layer sharing with other images and may use significantly more
+space.
+
+**Note**: using this option you may see significantly more space used due to
+storing two copies of the image, one for the build cache with all the cache
+layers in tact, and one for the squashed version.

+ 41 - 0
integration-cli/docker_cli_build_test.go

@@ -7195,3 +7195,44 @@ RUN ["cat", "/foo/file"]
 		c.Fatal(err)
 	}
 }
+
+func (s *DockerSuite) TestBuildSquashParent(c *check.C) {
+	testRequires(c, ExperimentalDaemon)
+	dockerFile := `
+		FROM busybox
+		RUN echo hello > /hello
+		RUN echo world >> /hello
+		RUN echo hello > /remove_me
+		ENV HELLO world
+		RUN rm /remove_me
+		`
+	// build and get the ID that we can use later for history comparison
+	origID, err := buildImage("test", dockerFile, false)
+	c.Assert(err, checker.IsNil)
+
+	// build with squash
+	id, err := buildImage("test", dockerFile, true, "--squash")
+	c.Assert(err, checker.IsNil)
+
+	out, _ := dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", "cat /hello")
+	c.Assert(strings.TrimSpace(out), checker.Equals, "hello\nworld")
+
+	dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", "[ ! -f /remove_me ]")
+	dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", `[ "$(echo $HELLO)" == "world" ]`)
+
+	// make sure the ID produced is the ID of the tag we specified
+	inspectID, err := inspectImage("test", ".ID")
+	c.Assert(err, checker.IsNil)
+	c.Assert(inspectID, checker.Equals, id)
+
+	origHistory, _ := dockerCmd(c, "history", origID)
+	testHistory, _ := dockerCmd(c, "history", "test")
+
+	splitOrigHistory := strings.Split(strings.TrimSpace(origHistory), "\n")
+	splitTestHistory := strings.Split(strings.TrimSpace(testHistory), "\n")
+	c.Assert(len(splitTestHistory), checker.Equals, len(splitOrigHistory)+1)
+
+	out, err = inspectImage(id, "len .RootFS.Layers")
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "3")
+}

+ 5 - 0
layer/empty.go

@@ -3,6 +3,7 @@ package layer
 import (
 	"archive/tar"
 	"bytes"
+	"fmt"
 	"io"
 	"io/ioutil"
 )
@@ -23,6 +24,10 @@ func (el *emptyLayer) TarStream() (io.ReadCloser, error) {
 	return ioutil.NopCloser(buf), nil
 }
 
+func (el *emptyLayer) TarStreamFrom(ChainID) (io.ReadCloser, error) {
+	return nil, fmt.Errorf("can't get parent tar stream of an empty layer")
+}
+
 func (el *emptyLayer) ChainID() ChainID {
 	return ChainID(DigestSHA256EmptyTar)
 }

+ 3 - 0
layer/layer.go

@@ -78,6 +78,9 @@ type TarStreamer interface {
 	// TarStream returns a tar archive stream
 	// for the contents of a layer.
 	TarStream() (io.ReadCloser, error)
+	// TarStreamFrom returns a tar archive stream for all the layer chain with
+	// arbitrary depth.
+	TarStreamFrom(ChainID) (io.ReadCloser, error)
 }
 
 // Layer represents a read-only layer

+ 9 - 5
layer/mounted_layer.go

@@ -1,6 +1,7 @@
 package layer
 
 import (
+	"fmt"
 	"io"
 
 	"github.com/docker/docker/pkg/archive"
@@ -28,11 +29,14 @@ func (ml *mountedLayer) cacheParent() string {
 }
 
 func (ml *mountedLayer) TarStream() (io.ReadCloser, error) {
-	archiver, err := ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
-	if err != nil {
-		return nil, err
-	}
-	return archiver, nil
+	return ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
+}
+
+func (ml *mountedLayer) TarStreamFrom(parent ChainID) (io.ReadCloser, error) {
+	// Not supported since this will include the init layer as well
+	// This can already be acheived with mount + tar.
+	// Should probably never reach this point, but error out here.
+	return nil, fmt.Errorf("getting a layer diff from an arbitrary parent is not supported on mounted layer")
 }
 
 func (ml *mountedLayer) Name() string {

+ 20 - 0
layer/ro_layer.go

@@ -21,6 +21,8 @@ type roLayer struct {
 	references     map[Layer]struct{}
 }
 
+// TarStream for roLayer guarentees that the data that is produced is the exact
+// data that the layer was registered with.
 func (rl *roLayer) TarStream() (io.ReadCloser, error) {
 	r, err := rl.layerStore.store.TarSplitReader(rl.chainID)
 	if err != nil {
@@ -43,6 +45,24 @@ func (rl *roLayer) TarStream() (io.ReadCloser, error) {
 	return rc, nil
 }
 
+// TarStreamFrom does not make any guarentees to the correctness of the produced
+// data. As such it should not be used when the layer content must be verified
+// to be an exact match to the registered layer.
+func (rl *roLayer) TarStreamFrom(parent ChainID) (io.ReadCloser, error) {
+	var parentCacheID string
+	for pl := rl.parent; pl != nil; pl = pl.parent {
+		if pl.chainID == parent {
+			parentCacheID = pl.cacheID
+			break
+		}
+	}
+
+	if parent != ChainID("") && parentCacheID == "" {
+		return nil, fmt.Errorf("layer ID '%s' is not a parent of the specified layer: cannot provide diff to non-parent", parent)
+	}
+	return rl.layerStore.driver.Diff(rl.cacheID, parentCacheID)
+}
+
 func (rl *roLayer) ChainID() ChainID {
 	return rl.chainID
 }

+ 17 - 0
man/docker-build.1.md

@@ -11,6 +11,7 @@ docker-build - Build a new image from the source code at PATH
 [**--cgroup-parent**[=*CGROUP-PARENT*]]
 [**--help**]
 [**-f**|**--file**[=*PATH/Dockerfile*]]
+[**-squash**] *Experimental*
 [**--force-rm**]
 [**--isolation**[=*default*]]
 [**--label**[=*[]*]]
@@ -57,6 +58,22 @@ set as the **URL**, the repository is cloned locally and then sent as the contex
    the remote context. In all cases, the file must be within the build context.
    The default is *Dockerfile*.
 
+**--squash**=*true*|*false*
+   **Experimental Only**
+   Once the image is built, squash the new layers into a new image with a single
+   new layer. Squashing does not destroy any existing image, rather it creates a new
+   image with the content of the squshed layers. This effectively makes it look
+   like all `Dockerfile` commands were created with a single layer. The build
+   cache is preserved with this method.
+
+   **Note**: using this option means the new image will not be able to take
+   advantage of layer sharing with other images and may use significantly more
+   space.
+
+   **Note**: using this option you may see significantly more space used due to
+   storing two copies of the image, one for the build cache with all the cache
+   layers in tact, and one for the squashed version.
+
 **--build-arg**=*variable*
    name and value of a **buildarg**.
 

+ 3 - 0
migrate/v1/migratev1_test.go

@@ -406,6 +406,9 @@ type mockLayer struct {
 func (l *mockLayer) TarStream() (io.ReadCloser, error) {
 	return nil, nil
 }
+func (l *mockLayer) TarStreamFrom(layer.ChainID) (io.ReadCloser, error) {
+	return nil, nil
+}
 
 func (l *mockLayer) ChainID() layer.ChainID {
 	return layer.CreateChainID(l.diffIDs)