Browse code

Remove CopyOnBuild from the daemon.

Add CreateImage() to the daemon
Refactor daemon.Comit() and expose a Image.NewChild()
Update copy to use IDMappings.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/05/15 03:18:48
Showing 16 changed files
... ...
@@ -11,6 +11,9 @@ import (
11 11
 	"github.com/docker/docker/api/types/backend"
12 12
 	"github.com/docker/docker/api/types/container"
13 13
 	containerpkg "github.com/docker/docker/container"
14
+	"github.com/docker/docker/image"
15
+	"github.com/docker/docker/layer"
16
+	"github.com/docker/docker/pkg/idtools"
14 17
 	"golang.org/x/net/context"
15 18
 )
16 19
 
... ...
@@ -42,11 +45,9 @@ type Backend interface {
42 42
 	// ContainerCreateWorkdir creates the workdir
43 43
 	ContainerCreateWorkdir(containerID string) error
44 44
 
45
-	// ContainerCopy copies/extracts a source FileInfo to a destination path inside a container
46
-	// specified by a container object.
47
-	// TODO: extract in the builder instead of passing `decompress`
48
-	// TODO: use containerd/fs.changestream instead as a source
49
-	CopyOnBuild(containerID string, destPath string, srcRoot string, srcPath string, decompress bool) error
45
+	CreateImage(config []byte, parent string) (string, error)
46
+
47
+	IDMappings() *idtools.IDMappings
50 48
 
51 49
 	ImageCacheBuilder
52 50
 }
... ...
@@ -96,10 +97,13 @@ type ImageCache interface {
96 96
 type Image interface {
97 97
 	ImageID() string
98 98
 	RunConfig() *container.Config
99
+	MarshalJSON() ([]byte, error)
100
+	NewChild(child image.ChildConfig) *image.Image
99 101
 }
100 102
 
101 103
 // ReleaseableLayer is an image layer that can be mounted and released
102 104
 type ReleaseableLayer interface {
103 105
 	Release() error
104 106
 	Mount() (string, error)
107
+	DiffID() layer.DiffID
105 108
 }
... ...
@@ -15,6 +15,8 @@ import (
15 15
 	"github.com/docker/docker/builder/dockerfile/command"
16 16
 	"github.com/docker/docker/builder/dockerfile/parser"
17 17
 	"github.com/docker/docker/builder/remotecontext"
18
+	"github.com/docker/docker/pkg/archive"
19
+	"github.com/docker/docker/pkg/chrootarchive"
18 20
 	"github.com/docker/docker/pkg/streamformatter"
19 21
 	"github.com/docker/docker/pkg/stringid"
20 22
 	"github.com/pkg/errors"
... ...
@@ -98,6 +100,7 @@ type Builder struct {
98 98
 	docker    builder.Backend
99 99
 	clientCtx context.Context
100 100
 
101
+	archiver         *archive.Archiver
101 102
 	buildStages      *buildStages
102 103
 	disableCommit    bool
103 104
 	buildArgs        *buildArgs
... ...
@@ -121,6 +124,7 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
121 121
 		Aux:              options.ProgressWriter.AuxFormatter,
122 122
 		Output:           options.ProgressWriter.Output,
123 123
 		docker:           options.Backend,
124
+		archiver:         chrootarchive.NewArchiver(options.Backend.IDMappings()),
124 125
 		buildArgs:        newBuildArgs(config.BuildArgs),
125 126
 		buildStages:      newBuildStages(),
126 127
 		imageSources:     newImageSources(clientCtx, options),
... ...
@@ -13,9 +13,12 @@ import (
13 13
 
14 14
 	"github.com/docker/docker/builder"
15 15
 	"github.com/docker/docker/builder/remotecontext"
16
+	"github.com/docker/docker/pkg/archive"
17
+	"github.com/docker/docker/pkg/idtools"
16 18
 	"github.com/docker/docker/pkg/ioutils"
17 19
 	"github.com/docker/docker/pkg/progress"
18 20
 	"github.com/docker/docker/pkg/streamformatter"
21
+	"github.com/docker/docker/pkg/symlink"
19 22
 	"github.com/docker/docker/pkg/system"
20 23
 	"github.com/docker/docker/pkg/urlutil"
21 24
 	"github.com/pkg/errors"
... ...
@@ -34,6 +37,10 @@ type copyInfo struct {
34 34
 	hash string
35 35
 }
36 36
 
37
+func (c copyInfo) fullPath() (string, error) {
38
+	return symlink.FollowSymlinkInScope(filepath.Join(c.root, c.path), c.root)
39
+}
40
+
37 41
 func newCopyInfoFromSource(source builder.Source, path string, hash string) copyInfo {
38 42
 	return copyInfo{root: source.Root(), path: path, hash: hash}
39 43
 }
... ...
@@ -355,3 +362,53 @@ func downloadSource(output io.Writer, stdout io.Writer, srcURL string) (remote b
355 355
 	lc, err := remotecontext.NewLazyContext(tmpDir)
356 356
 	return lc, filename, err
357 357
 }
358
+
359
+type copyFileOptions struct {
360
+	decompress bool
361
+	archiver   *archive.Archiver
362
+}
363
+
364
+func copyFile(dest copyInfo, source copyInfo, options copyFileOptions) error {
365
+	srcPath, err := source.fullPath()
366
+	if err != nil {
367
+		return err
368
+	}
369
+	destPath, err := dest.fullPath()
370
+	if err != nil {
371
+		return err
372
+	}
373
+
374
+	archiver := options.archiver
375
+	rootIDs := archiver.IDMappings.RootPair()
376
+
377
+	src, err := os.Stat(srcPath)
378
+	if err != nil {
379
+		return err // TODO: errors.Wrapf
380
+	}
381
+	if src.IsDir() {
382
+		if err := archiver.CopyWithTar(srcPath, destPath); err != nil {
383
+			return err
384
+		}
385
+		return fixPermissions(srcPath, destPath, rootIDs)
386
+	}
387
+
388
+	if options.decompress && archive.IsArchivePath(srcPath) {
389
+		// To support the untar feature we need to clean up the path a little bit
390
+		// because tar is not very forgiving
391
+		tarDest := dest.path
392
+		// TODO: could this be just TrimSuffix()?
393
+		if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
394
+			tarDest = filepath.Dir(dest.path)
395
+		}
396
+		return archiver.UntarPath(srcPath, tarDest)
397
+	}
398
+
399
+	if err := idtools.MkdirAllAndChownNew(filepath.Dir(destPath), 0755, rootIDs); err != nil {
400
+		return err
401
+	}
402
+	if err := archiver.CopyFileWithTar(srcPath, destPath); err != nil {
403
+		return err
404
+	}
405
+	// TODO: do I have to change destPath to the filename?
406
+	return fixPermissions(srcPath, destPath, rootIDs)
407
+}
358 408
new file mode 100644
... ...
@@ -0,0 +1,64 @@
0
+package dockerfile
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+
6
+	"github.com/docker/docker/pkg/idtools"
7
+)
8
+
9
+func pathExists(path string) (bool, error) {
10
+	_, err := os.Stat(path)
11
+	switch {
12
+	case err == nil:
13
+		return true, nil
14
+	case os.IsNotExist(err):
15
+		return false, nil
16
+	}
17
+	return false, err
18
+}
19
+
20
+// TODO: review this
21
+func fixPermissions(source, destination string, rootIDs idtools.IDPair) error {
22
+	doChownDestination, err := chownDestinationRoot(destination)
23
+	if err != nil {
24
+		return err
25
+	}
26
+
27
+	// We Walk on the source rather than on the destination because we don't
28
+	// want to change permissions on things we haven't created or modified.
29
+	return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
30
+		// Do not alter the walk root iff. it existed before, as it doesn't fall under
31
+		// the domain of "things we should chown".
32
+		if !doChownDestination && (source == fullpath) {
33
+			return nil
34
+		}
35
+
36
+		// Path is prefixed by source: substitute with destination instead.
37
+		cleaned, err := filepath.Rel(source, fullpath)
38
+		if err != nil {
39
+			return err
40
+		}
41
+
42
+		fullpath = filepath.Join(destination, cleaned)
43
+		return os.Lchown(fullpath, rootIDs.UID, rootIDs.GID)
44
+	})
45
+}
46
+
47
+// If the destination didn't already exist, or the destination isn't a
48
+// directory, then we should Lchown the destination. Otherwise, we shouldn't
49
+// Lchown the destination.
50
+func chownDestinationRoot(destination string) (bool, error) {
51
+	destExists, err := pathExists(destination)
52
+	if err != nil {
53
+		return false, err
54
+	}
55
+	destStat, err := os.Stat(destination)
56
+	if err != nil {
57
+		// This should *never* be reached, because the destination must've already
58
+		// been created while untar-ing the context.
59
+		return false, err
60
+	}
61
+
62
+	return !destExists || !destStat.IsDir(), nil
63
+}
0 64
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+package dockerfile
1
+
2
+import "github.com/docker/docker/pkg/idtools"
3
+
4
+func fixPermissions(source, destination string, rootIDs idtools.IDPair) error {
5
+	// chown is not supported on Windows
6
+	return nil
7
+}
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/api/types/strslice"
24 24
 	"github.com/docker/docker/builder"
25 25
 	"github.com/docker/docker/builder/dockerfile/parser"
26
+	"github.com/docker/docker/image"
26 27
 	"github.com/docker/docker/pkg/jsonmessage"
27 28
 	"github.com/docker/docker/pkg/signal"
28 29
 	"github.com/docker/go-connections/nat"
... ...
@@ -251,10 +252,8 @@ func parseBuildStageName(args []string) (string, error) {
251 251
 	return stageName, nil
252 252
 }
253 253
 
254
-// scratchImage is used as a token for the empty base image. It uses buildStage
255
-// as a convenient implementation of builder.Image, but is not actually a
256
-// buildStage.
257
-var scratchImage builder.Image = &buildStage{}
254
+// scratchImage is used as a token for the empty base image.
255
+var scratchImage builder.Image = &image.Image{}
258 256
 
259 257
 func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) {
260 258
 	substitutionArgs := []string{}
... ...
@@ -267,8 +266,8 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err
267 267
 		return nil, err
268 268
 	}
269 269
 
270
-	if im, ok := b.buildStages.getByName(name); ok {
271
-		return im, nil
270
+	if stage, ok := b.buildStages.getByName(name); ok {
271
+		name = stage.ImageID()
272 272
 	}
273 273
 
274 274
 	// Windows cannot support a container with no base image.
... ...
@@ -6,37 +6,29 @@ import (
6 6
 
7 7
 	"github.com/Sirupsen/logrus"
8 8
 	"github.com/docker/docker/api/types/backend"
9
-	"github.com/docker/docker/api/types/container"
10 9
 	"github.com/docker/docker/builder"
11 10
 	"github.com/docker/docker/builder/remotecontext"
11
+	"github.com/docker/docker/layer"
12 12
 	"github.com/pkg/errors"
13 13
 	"golang.org/x/net/context"
14 14
 )
15 15
 
16 16
 type buildStage struct {
17
-	id     string
18
-	config *container.Config
17
+	id string
19 18
 }
20 19
 
21
-func newBuildStageFromImage(image builder.Image) *buildStage {
22
-	return &buildStage{id: image.ImageID(), config: image.RunConfig()}
20
+func newBuildStage(imageID string) *buildStage {
21
+	return &buildStage{id: imageID}
23 22
 }
24 23
 
25 24
 func (b *buildStage) ImageID() string {
26 25
 	return b.id
27 26
 }
28 27
 
29
-func (b *buildStage) RunConfig() *container.Config {
30
-	return b.config
31
-}
32
-
33
-func (b *buildStage) update(imageID string, runConfig *container.Config) {
28
+func (b *buildStage) update(imageID string) {
34 29
 	b.id = imageID
35
-	b.config = runConfig
36 30
 }
37 31
 
38
-var _ builder.Image = &buildStage{}
39
-
40 32
 // buildStages tracks each stage of a build so they can be retrieved by index
41 33
 // or by name.
42 34
 type buildStages struct {
... ...
@@ -48,12 +40,12 @@ func newBuildStages() *buildStages {
48 48
 	return &buildStages{byName: make(map[string]*buildStage)}
49 49
 }
50 50
 
51
-func (s *buildStages) getByName(name string) (builder.Image, bool) {
51
+func (s *buildStages) getByName(name string) (*buildStage, bool) {
52 52
 	stage, ok := s.byName[strings.ToLower(name)]
53 53
 	return stage, ok
54 54
 }
55 55
 
56
-func (s *buildStages) get(indexOrName string) (builder.Image, error) {
56
+func (s *buildStages) get(indexOrName string) (*buildStage, error) {
57 57
 	index, err := strconv.Atoi(indexOrName)
58 58
 	if err == nil {
59 59
 		if err := s.validateIndex(index); err != nil {
... ...
@@ -78,7 +70,7 @@ func (s *buildStages) validateIndex(i int) error {
78 78
 }
79 79
 
80 80
 func (s *buildStages) add(name string, image builder.Image) error {
81
-	stage := newBuildStageFromImage(image)
81
+	stage := newBuildStage(image.ImageID())
82 82
 	name = strings.ToLower(name)
83 83
 	if len(name) > 0 {
84 84
 		if _, ok := s.byName[name]; ok {
... ...
@@ -90,8 +82,8 @@ func (s *buildStages) add(name string, image builder.Image) error {
90 90
 	return nil
91 91
 }
92 92
 
93
-func (s *buildStages) update(imageID string, runConfig *container.Config) {
94
-	s.sequence[len(s.sequence)-1].update(imageID, runConfig)
93
+func (s *buildStages) update(imageID string) {
94
+	s.sequence[len(s.sequence)-1].update(imageID)
95 95
 }
96 96
 
97 97
 type getAndMountFunc func(string) (builder.Image, builder.ReleaseableLayer, error)
... ...
@@ -190,3 +182,7 @@ func (im *imageMount) Image() builder.Image {
190 190
 func (im *imageMount) ImageID() string {
191 191
 	return im.image.ImageID()
192 192
 }
193
+
194
+func (im *imageMount) DiffID() layer.DiffID {
195
+	return im.layer.DiffID()
196
+}
... ...
@@ -12,6 +12,8 @@ import (
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/backend"
14 14
 	"github.com/docker/docker/api/types/container"
15
+	"github.com/docker/docker/builder"
16
+	"github.com/docker/docker/image"
15 17
 	"github.com/docker/docker/pkg/stringid"
16 18
 	"github.com/pkg/errors"
17 19
 )
... ...
@@ -37,7 +39,6 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
37 37
 	return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
38 38
 }
39 39
 
40
-// TODO: see if any args can be dropped
41 40
 func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
42 41
 	if b.disableCommit {
43 42
 		return nil
... ...
@@ -60,10 +61,20 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
60 60
 	}
61 61
 
62 62
 	dispatchState.imageID = imageID
63
-	b.buildStages.update(imageID, dispatchState.runConfig)
63
+	b.buildStages.update(imageID)
64 64
 	return nil
65 65
 }
66 66
 
67
+func (b *Builder) exportImage(state *dispatchState, image builder.Image) error {
68
+	config, err := image.MarshalJSON()
69
+	if err != nil {
70
+		return errors.Wrap(err, "failed to encode image config")
71
+	}
72
+
73
+	state.imageID, err = b.docker.CreateImage(config, state.imageID)
74
+	return err
75
+}
76
+
67 77
 func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
68 78
 	srcHash := getSourceHashFromInfos(inst.infos)
69 79
 
... ...
@@ -83,12 +94,34 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
83 83
 		return err
84 84
 	}
85 85
 
86
+	imageMount, err := b.imageSources.Get(state.imageID)
87
+	if err != nil {
88
+		return err
89
+	}
90
+	destSource, err := imageMount.Source()
91
+	if err != nil {
92
+		return err
93
+	}
94
+
95
+	destInfo := newCopyInfoFromSource(destSource, dest, "")
96
+	opts := copyFileOptions{
97
+		decompress: inst.allowLocalDecompression,
98
+		archiver:   b.archiver,
99
+	}
86 100
 	for _, info := range inst.infos {
87
-		if err := b.docker.CopyOnBuild(containerID, dest, info.root, info.path, inst.allowLocalDecompression); err != nil {
101
+		if err := copyFile(destInfo, info, opts); err != nil {
88 102
 			return err
89 103
 		}
90 104
 	}
91
-	return b.commitContainer(state, containerID, runConfigWithCommentCmd)
105
+
106
+	newImage := imageMount.Image().NewChild(image.ChildConfig{
107
+		Author:          state.maintainer,
108
+		DiffID:          imageMount.DiffID(),
109
+		ContainerConfig: runConfigWithCommentCmd,
110
+		// TODO: ContainerID?
111
+		// TODO: Config?
112
+	})
113
+	return b.exportImage(state, newImage)
92 114
 }
93 115
 
94 116
 // For backwards compat, if there's just one info then use it as the
... ...
@@ -182,7 +215,7 @@ func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.
182 182
 	fmt.Fprint(b.Stdout, " ---> Using cache\n")
183 183
 
184 184
 	dispatchState.imageID = string(cachedID)
185
-	b.buildStages.update(dispatchState.imageID, runConfig)
185
+	b.buildStages.update(dispatchState.imageID)
186 186
 	return true, nil
187 187
 }
188 188
 
... ...
@@ -1,6 +1,7 @@
1 1
 package dockerfile
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"io"
5 6
 
6 7
 	"github.com/docker/docker/api/types"
... ...
@@ -8,6 +9,9 @@ import (
8 8
 	"github.com/docker/docker/api/types/container"
9 9
 	"github.com/docker/docker/builder"
10 10
 	containerpkg "github.com/docker/docker/container"
11
+	"github.com/docker/docker/image"
12
+	"github.com/docker/docker/layer"
13
+	"github.com/docker/docker/pkg/idtools"
11 14
 	"golang.org/x/net/context"
12 15
 )
13 16
 
... ...
@@ -76,6 +80,14 @@ func (m *MockBackend) MakeImageCache(cacheFrom []string) builder.ImageCache {
76 76
 	return nil
77 77
 }
78 78
 
79
+func (m *MockBackend) CreateImage(config []byte, parent string) (string, error) {
80
+	return "c411d1d", nil
81
+}
82
+
83
+func (m *MockBackend) IDMappings() *idtools.IDMappings {
84
+	return &idtools.IDMappings{}
85
+}
86
+
79 87
 type mockImage struct {
80 88
 	id     string
81 89
 	config *container.Config
... ...
@@ -89,6 +101,15 @@ func (i *mockImage) RunConfig() *container.Config {
89 89
 	return i.config
90 90
 }
91 91
 
92
+func (i *mockImage) MarshalJSON() ([]byte, error) {
93
+	type rawImage mockImage
94
+	return json.Marshal(rawImage(*i))
95
+}
96
+
97
+func (i *mockImage) NewChild(child image.ChildConfig) *image.Image {
98
+	return nil
99
+}
100
+
92 101
 type mockImageCache struct {
93 102
 	getCacheFunc func(parentID string, cfg *container.Config) (string, error)
94 103
 }
... ...
@@ -109,3 +130,7 @@ func (l *mockLayer) Release() error {
109 109
 func (l *mockLayer) Mount() (string, error) {
110 110
 	return "mountPath", nil
111 111
 }
112
+
113
+func (l *mockLayer) DiffID() layer.DiffID {
114
+	return layer.DiffID("abcdef12345")
115
+}
... ...
@@ -10,9 +10,7 @@ import (
10 10
 	"github.com/docker/docker/container"
11 11
 	"github.com/docker/docker/pkg/archive"
12 12
 	"github.com/docker/docker/pkg/chrootarchive"
13
-	"github.com/docker/docker/pkg/idtools"
14 13
 	"github.com/docker/docker/pkg/ioutils"
15
-	"github.com/docker/docker/pkg/symlink"
16 14
 	"github.com/docker/docker/pkg/system"
17 15
 	"github.com/pkg/errors"
18 16
 )
... ...
@@ -361,104 +359,4 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
361 361
 	})
362 362
 	daemon.LogContainerEvent(container, "copy")
363 363
 	return reader, nil
364
-}
365
-
366
-// CopyOnBuild copies/extracts a source FileInfo to a destination path inside a container
367
-// specified by a container object.
368
-// TODO: make sure callers don't unnecessarily convert destPath with filepath.FromSlash (Copy does it already).
369
-// CopyOnBuild should take in abstract paths (with slashes) and the implementation should convert it to OS-specific paths.
370
-func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decompress bool) error {
371
-	fullSrcPath, err := symlink.FollowSymlinkInScope(filepath.Join(srcRoot, srcPath), srcRoot)
372
-	if err != nil {
373
-		return err
374
-	}
375
-
376
-	destExists := true
377
-	destDir := false
378
-	rootIDs := daemon.idMappings.RootPair()
379
-
380
-	// Work in daemon-local OS specific file paths
381
-	destPath = filepath.FromSlash(destPath)
382
-
383
-	c, err := daemon.GetContainer(cID)
384
-	if err != nil {
385
-		return err
386
-	}
387
-	err = daemon.Mount(c)
388
-	if err != nil {
389
-		return err
390
-	}
391
-	defer daemon.Unmount(c)
392
-
393
-	dest, err := c.GetResourcePath(destPath)
394
-	if err != nil {
395
-		return err
396
-	}
397
-
398
-	// Preserve the trailing slash
399
-	// TODO: why are we appending another path separator if there was already one?
400
-	if strings.HasSuffix(destPath, string(os.PathSeparator)) || destPath == "." {
401
-		destDir = true
402
-		dest += string(os.PathSeparator)
403
-	}
404
-
405
-	destPath = dest
406
-
407
-	destStat, err := os.Stat(destPath)
408
-	if err != nil {
409
-		if !os.IsNotExist(err) {
410
-			//logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
411
-			return err
412
-		}
413
-		destExists = false
414
-	}
415
-
416
-	archiver := chrootarchive.NewArchiver(daemon.idMappings)
417
-	src, err := os.Stat(fullSrcPath)
418
-	if err != nil {
419
-		return err
420
-	}
421
-
422
-	if src.IsDir() {
423
-		// copy as directory
424
-		if err := archiver.CopyWithTar(fullSrcPath, destPath); err != nil {
425
-			return err
426
-		}
427
-		return fixPermissions(fullSrcPath, destPath, rootIDs.UID, rootIDs.GID, destExists)
428
-	}
429
-	if decompress && archive.IsArchivePath(fullSrcPath) {
430
-		// Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file)
431
-
432
-		// First try to unpack the source as an archive
433
-		// to support the untar feature we need to clean up the path a little bit
434
-		// because tar is very forgiving.  First we need to strip off the archive's
435
-		// filename from the path but this is only added if it does not end in slash
436
-		tarDest := destPath
437
-		if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
438
-			tarDest = filepath.Dir(destPath)
439
-		}
440
-
441
-		// try to successfully untar the orig
442
-		err := archiver.UntarPath(fullSrcPath, tarDest)
443
-		/*
444
-			if err != nil {
445
-				logrus.Errorf("Couldn't untar to %s: %v", tarDest, err)
446
-			}
447
-		*/
448
-		return err
449
-	}
450
-
451
-	// only needed for fixPermissions, but might as well put it before CopyFileWithTar
452
-	if destDir || (destExists && destStat.IsDir()) {
453
-		destPath = filepath.Join(destPath, filepath.Base(srcPath))
454
-	}
455
-
456
-	if err := idtools.MkdirAllAndChownNew(filepath.Dir(destPath), 0755, rootIDs); err != nil {
457
-		return err
458
-	}
459
-	if err := archiver.CopyFileWithTar(fullSrcPath, destPath); err != nil {
460
-		return err
461
-	}
462
-
463
-	return fixPermissions(fullSrcPath, destPath, rootIDs.UID, rootIDs.GID, destExists)
464
-}
364
+}
465 365
\ No newline at end of file
... ...
@@ -3,9 +3,6 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
-	"os"
7
-	"path/filepath"
8
-
9 6
 	"github.com/docker/docker/container"
10 7
 )
11 8
 
... ...
@@ -25,38 +22,6 @@ func checkIfPathIsInAVolume(container *container.Container, absPath string) (boo
25 25
 	return toVolume, nil
26 26
 }
27 27
 
28
-func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
29
-	// If the destination didn't already exist, or the destination isn't a
30
-	// directory, then we should Lchown the destination. Otherwise, we shouldn't
31
-	// Lchown the destination.
32
-	destStat, err := os.Stat(destination)
33
-	if err != nil {
34
-		// This should *never* be reached, because the destination must've already
35
-		// been created while untar-ing the context.
36
-		return err
37
-	}
38
-	doChownDestination := !destExisted || !destStat.IsDir()
39
-
40
-	// We Walk on the source rather than on the destination because we don't
41
-	// want to change permissions on things we haven't created or modified.
42
-	return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
43
-		// Do not alter the walk root iff. it existed before, as it doesn't fall under
44
-		// the domain of "things we should chown".
45
-		if !doChownDestination && (source == fullpath) {
46
-			return nil
47
-		}
48
-
49
-		// Path is prefixed by source: substitute with destination instead.
50
-		cleaned, err := filepath.Rel(source, fullpath)
51
-		if err != nil {
52
-			return err
53
-		}
54
-
55
-		fullpath = filepath.Join(destination, cleaned)
56
-		return os.Lchown(fullpath, uid, gid)
57
-	})
58
-}
59
-
60 28
 // isOnlineFSOperationPermitted returns an error if an online filesystem operation
61 29
 // is not permitted.
62 30
 func (daemon *Daemon) isOnlineFSOperationPermitted(container *container.Container) error {
... ...
@@ -17,11 +17,6 @@ func checkIfPathIsInAVolume(container *container.Container, absPath string) (boo
17 17
 	return false, nil
18 18
 }
19 19
 
20
-func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
21
-	// chown is not supported on Windows
22
-	return nil
23
-}
24
-
25 20
 // isOnlineFSOperationPermitted returns an error if an online filesystem operation
26 21
 // is not permitted (such as stat or for copying). Running Hyper-V containers
27 22
 // cannot have their file-system interrogated from the host as the filter is
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/builder"
9 9
 	"github.com/docker/docker/image"
10 10
 	"github.com/docker/docker/layer"
11
+	"github.com/docker/docker/pkg/idtools"
11 12
 	"github.com/docker/docker/pkg/stringid"
12 13
 	"github.com/docker/docker/registry"
13 14
 	"github.com/pkg/errors"
... ...
@@ -40,6 +41,10 @@ func (rl *releaseableLayer) Release() error {
40 40
 	return rl.releaseROLayer()
41 41
 }
42 42
 
43
+func (rl *releaseableLayer) DiffID() layer.DiffID {
44
+	return rl.roLayer.DiffID()
45
+}
46
+
43 47
 func (rl *releaseableLayer) releaseRWLayer() error {
44 48
 	if rl.rwLayer == nil {
45 49
 		return nil
... ...
@@ -120,3 +125,26 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
120 120
 	layer, err := newReleasableLayerForImage(image, daemon.layerStore)
121 121
 	return image, layer, err
122 122
 }
123
+
124
+// CreateImage creates a new image by adding a config and ID to the image store.
125
+// This is similar to LoadImage() except that it receives JSON encoded bytes of
126
+// an image instead of a tar archive.
127
+func (daemon *Daemon) CreateImage(config []byte, parent string) (string, error) {
128
+	id, err := daemon.imageStore.Create(config)
129
+	if err != nil {
130
+		return "", err
131
+	}
132
+
133
+	if parent != "" {
134
+		if err := daemon.imageStore.SetParent(id, image.ID(parent)); err != nil {
135
+			return "", err
136
+		}
137
+	}
138
+	// TODO: do we need any daemon.LogContainerEventWithAttributes?
139
+	return id.String(), nil
140
+}
141
+
142
+// IDMappings returns uid/gid mappings for the builder
143
+func (daemon *Daemon) IDMappings() *idtools.IDMappings {
144
+	return daemon.idMappings
145
+}
... ...
@@ -12,7 +12,6 @@ import (
12 12
 	containertypes "github.com/docker/docker/api/types/container"
13 13
 	"github.com/docker/docker/builder/dockerfile"
14 14
 	"github.com/docker/docker/container"
15
-	"github.com/docker/docker/dockerversion"
16 15
 	"github.com/docker/docker/image"
17 16
 	"github.com/docker/docker/layer"
18 17
 	"github.com/docker/docker/pkg/ioutils"
... ...
@@ -129,11 +128,6 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
129 129
 		return "", err
130 130
 	}
131 131
 
132
-	containerConfig := c.ContainerConfig
133
-	if containerConfig == nil {
134
-		containerConfig = container.Config
135
-	}
136
-
137 132
 	// It is not possible to commit a running container on Windows and on Solaris.
138 133
 	if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() {
139 134
 		return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS)
... ...
@@ -165,60 +159,36 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
165 165
 		}
166 166
 	}()
167 167
 
168
-	var history []image.History
169
-	rootFS := image.NewRootFS()
170
-	osVersion := ""
171
-	var osFeatures []string
172
-
173
-	if container.ImageID != "" {
174
-		img, err := daemon.imageStore.Get(container.ImageID)
168
+	var parent *image.Image
169
+	if container.ImageID == "" {
170
+		parent = new(image.Image)
171
+		parent.RootFS = image.NewRootFS()
172
+	} else {
173
+		parent, err = daemon.imageStore.Get(container.ImageID)
175 174
 		if err != nil {
176 175
 			return "", err
177 176
 		}
178
-		history = img.History
179
-		rootFS = img.RootFS
180
-		osVersion = img.OSVersion
181
-		osFeatures = img.OSFeatures
182 177
 	}
183 178
 
184
-	l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID())
179
+	l, err := daemon.layerStore.Register(rwTar, parent.RootFS.ChainID())
185 180
 	if err != nil {
186 181
 		return "", err
187 182
 	}
188 183
 	defer layer.ReleaseAndLog(daemon.layerStore, l)
189 184
 
190
-	h := image.History{
191
-		Author:     c.Author,
192
-		Created:    time.Now().UTC(),
193
-		CreatedBy:  strings.Join(containerConfig.Cmd, " "),
194
-		Comment:    c.Comment,
195
-		EmptyLayer: true,
185
+	containerConfig := c.ContainerConfig
186
+	if containerConfig == nil {
187
+		containerConfig = container.Config
196 188
 	}
197
-
198
-	if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID {
199
-		h.EmptyLayer = false
200
-		rootFS.Append(diffID)
189
+	cc := image.ChildConfig{
190
+		ContainerID:     container.ID,
191
+		Author:          c.Author,
192
+		Comment:         c.Comment,
193
+		ContainerConfig: containerConfig,
194
+		Config:          newConfig,
195
+		DiffID:          l.DiffID(),
201 196
 	}
202
-
203
-	history = append(history, h)
204
-
205
-	config, err := json.Marshal(&image.Image{
206
-		V1Image: image.V1Image{
207
-			DockerVersion:   dockerversion.Version,
208
-			Config:          newConfig,
209
-			Architecture:    runtime.GOARCH,
210
-			OS:              runtime.GOOS,
211
-			Container:       container.ID,
212
-			ContainerConfig: *containerConfig,
213
-			Author:          c.Author,
214
-			Created:         h.Created,
215
-		},
216
-		RootFS:     rootFS,
217
-		History:    history,
218
-		OSFeatures: osFeatures,
219
-		OSVersion:  osVersion,
220
-	})
221
-
197
+	config, err := json.Marshal(parent.NewChild(cc))
222 198
 	if err != nil {
223 199
 		return "", err
224 200
 	}
... ...
@@ -7,7 +7,11 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"github.com/docker/docker/api/types/container"
10
+	"github.com/docker/docker/dockerversion"
11
+	"github.com/docker/docker/layer"
10 12
 	"github.com/opencontainers/go-digest"
13
+	"runtime"
14
+	"strings"
11 15
 )
12 16
 
13 17
 // ID is the content-addressable ID of an image.
... ...
@@ -110,6 +114,48 @@ func (img *Image) MarshalJSON() ([]byte, error) {
110 110
 	return json.Marshal(c)
111 111
 }
112 112
 
113
+// ChildConfig is the configuration to apply to an Image to create a new
114
+// Child image. Other properties of the image are copied from the parent.
115
+type ChildConfig struct {
116
+	ContainerID     string
117
+	Author          string
118
+	Comment         string
119
+	DiffID          layer.DiffID
120
+	ContainerConfig *container.Config
121
+	Config          *container.Config
122
+}
123
+
124
+// NewChild creates a new Image as a child of this image.
125
+func (img *Image) NewChild(child ChildConfig) *Image {
126
+	isEmptyLayer := layer.IsEmpty(child.DiffID)
127
+	rootFS := img.RootFS
128
+	if !isEmptyLayer {
129
+		rootFS.Append(child.DiffID)
130
+	}
131
+	imgHistory := NewHistory(
132
+		child.Author,
133
+		child.Comment,
134
+		strings.Join(child.ContainerConfig.Cmd, " "),
135
+		isEmptyLayer)
136
+
137
+	return &Image{
138
+		V1Image: V1Image{
139
+			DockerVersion:   dockerversion.Version,
140
+			Config:          child.Config,
141
+			Architecture:    runtime.GOARCH,
142
+			OS:              runtime.GOOS,
143
+			Container:       child.ContainerID,
144
+			ContainerConfig: *child.ContainerConfig,
145
+			Author:          child.Author,
146
+			Created:         imgHistory.Created,
147
+		},
148
+		RootFS:     rootFS,
149
+		History:    append(img.History, imgHistory),
150
+		OSFeatures: img.OSFeatures,
151
+		OSVersion:  img.OSVersion,
152
+	}
153
+}
154
+
113 155
 // History stores build commands that were used to create an image
114 156
 type History struct {
115 157
 	// Created is the timestamp at which the image was created
... ...
@@ -126,6 +172,18 @@ type History struct {
126 126
 	EmptyLayer bool `json:"empty_layer,omitempty"`
127 127
 }
128 128
 
129
+// NewHistory creates a new history struct from arguments, and sets the created
130
+// time to the current time in UTC
131
+func NewHistory(author, comment, createdBy string, isEmptyLayer bool) History {
132
+	return History{
133
+		Author:     author,
134
+		Created:    time.Now().UTC(),
135
+		CreatedBy:  createdBy,
136
+		Comment:    comment,
137
+		EmptyLayer: isEmptyLayer,
138
+	}
139
+}
140
+
129 141
 // Exporter provides interface for loading and saving images
130 142
 type Exporter interface {
131 143
 	Load(io.ReadCloser, io.Writer, bool) error
... ...
@@ -54,3 +54,8 @@ func (el *emptyLayer) DiffSize() (size int64, err error) {
54 54
 func (el *emptyLayer) Metadata() (map[string]string, error) {
55 55
 	return make(map[string]string), nil
56 56
 }
57
+
58
+// IsEmpty returns true if the layer is an EmptyLayer
59
+func IsEmpty(diffID DiffID) bool {
60
+	return diffID == DigestSHA256EmptyTar
61
+}