Browse code

Merge pull request #9123 from rhatdan/commit-change

Patch to commit-change patch to add docker import support

Tibor Vass authored on 2015/02/25 07:09:10
Showing 13 changed files
... ...
@@ -1156,6 +1156,8 @@ func (cli *DockerCli) CmdKill(args ...string) error {
1156 1156
 
1157 1157
 func (cli *DockerCli) CmdImport(args ...string) error {
1158 1158
 	cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
1159
+	flChanges := opts.NewListOpts(nil)
1160
+	cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image.")
1159 1161
 	cmd.Require(flag.Min, 1)
1160 1162
 
1161 1163
 	utils.ParseFlags(cmd, args, true)
... ...
@@ -1168,7 +1170,9 @@ func (cli *DockerCli) CmdImport(args ...string) error {
1168 1168
 
1169 1169
 	v.Set("fromSrc", src)
1170 1170
 	v.Set("repo", repository)
1171
-
1171
+	for _, change := range flChanges.GetAll() {
1172
+		v.Add("changes", change)
1173
+	}
1172 1174
 	if cmd.NArg() == 3 {
1173 1175
 		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n")
1174 1176
 		v.Set("tag", cmd.Arg(2))
... ...
@@ -1702,6 +1706,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1702 1702
 	flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
1703 1703
 	flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
1704 1704
 	flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
1705
+	flChanges := opts.NewListOpts(nil)
1706
+	cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image.")
1705 1707
 	// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
1706 1708
 	flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
1707 1709
 	cmd.Require(flag.Max, 2)
... ...
@@ -1726,6 +1732,9 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1726 1726
 	v.Set("tag", tag)
1727 1727
 	v.Set("comment", *flComment)
1728 1728
 	v.Set("author", *flAuthor)
1729
+	for _, change := range flChanges.GetAll() {
1730
+		v.Add("changes", change)
1731
+	}
1729 1732
 
1730 1733
 	if *flPause != true {
1731 1734
 		v.Set("pause", "0")
... ...
@@ -518,6 +518,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
518 518
 	job.Setenv("tag", r.Form.Get("tag"))
519 519
 	job.Setenv("author", r.Form.Get("author"))
520 520
 	job.Setenv("comment", r.Form.Get("comment"))
521
+	job.SetenvList("changes", r.Form["changes"])
521 522
 	job.SetenvSubEnv("config", &config)
522 523
 
523 524
 	job.Stdout.Add(stdoutBuffer)
... ...
@@ -570,6 +571,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon
570 570
 		}
571 571
 		job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag)
572 572
 		job.Stdin.Add(r.Body)
573
+		job.SetenvList("changes", r.Form["changes"])
573 574
 	}
574 575
 
575 576
 	if version.GreaterThan("1.0") {
... ...
@@ -96,6 +96,11 @@ type Builder struct {
96 96
 	ForceRemove bool
97 97
 	Pull        bool
98 98
 
99
+	// set this to true if we want the builder to not commit between steps.
100
+	// This is useful when we only want to use the evaluator table to generate
101
+	// the final configs of the Dockerfile but dont want the layers
102
+	disableCommit bool
103
+
99 104
 	AuthConfig     *registry.AuthConfig
100 105
 	AuthConfigFile *registry.ConfigFile
101 106
 
... ...
@@ -60,6 +60,9 @@ func (b *Builder) readContext(context io.Reader) error {
60 60
 }
61 61
 
62 62
 func (b *Builder) commit(id string, autoCmd []string, comment string) error {
63
+	if b.disableCommit {
64
+		return nil
65
+	}
63 66
 	if b.image == "" && !b.noBaseImage {
64 67
 		return fmt.Errorf("Please provide a source image with `from` prior to commit")
65 68
 	}
... ...
@@ -1,12 +1,16 @@
1 1
 package builder
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"encoding/json"
4 6
 	"io"
5 7
 	"io/ioutil"
6 8
 	"os"
7 9
 	"os/exec"
10
+	"strings"
8 11
 
9 12
 	"github.com/docker/docker/api"
13
+	"github.com/docker/docker/builder/parser"
10 14
 	"github.com/docker/docker/daemon"
11 15
 	"github.com/docker/docker/engine"
12 16
 	"github.com/docker/docker/graph"
... ...
@@ -14,9 +18,22 @@ import (
14 14
 	"github.com/docker/docker/pkg/parsers"
15 15
 	"github.com/docker/docker/pkg/urlutil"
16 16
 	"github.com/docker/docker/registry"
17
+	"github.com/docker/docker/runconfig"
17 18
 	"github.com/docker/docker/utils"
18 19
 )
19 20
 
21
+// whitelist of commands allowed for a commit/import
22
+var validCommitCommands = map[string]bool{
23
+	"entrypoint": true,
24
+	"cmd":        true,
25
+	"user":       true,
26
+	"workdir":    true,
27
+	"env":        true,
28
+	"volume":     true,
29
+	"expose":     true,
30
+	"onbuild":    true,
31
+}
32
+
20 33
 type BuilderJob struct {
21 34
 	Engine *engine.Engine
22 35
 	Daemon *daemon.Daemon
... ...
@@ -24,6 +41,7 @@ type BuilderJob struct {
24 24
 
25 25
 func (b *BuilderJob) Install() {
26 26
 	b.Engine.Register("build", b.CmdBuild)
27
+	b.Engine.Register("build_config", b.CmdBuildConfig)
27 28
 }
28 29
 
29 30
 func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
... ...
@@ -138,3 +156,50 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
138 138
 	}
139 139
 	return engine.StatusOK
140 140
 }
141
+
142
+func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status {
143
+	if len(job.Args) != 0 {
144
+		return job.Errorf("Usage: %s\n", job.Name)
145
+	}
146
+
147
+	var (
148
+		changes   = job.GetenvList("changes")
149
+		newConfig runconfig.Config
150
+	)
151
+
152
+	if err := job.GetenvJson("config", &newConfig); err != nil {
153
+		return job.Error(err)
154
+	}
155
+
156
+	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
157
+	if err != nil {
158
+		return job.Error(err)
159
+	}
160
+
161
+	// ensure that the commands are valid
162
+	for _, n := range ast.Children {
163
+		if !validCommitCommands[n.Value] {
164
+			return job.Errorf("%s is not a valid change command", n.Value)
165
+		}
166
+	}
167
+
168
+	builder := &Builder{
169
+		Daemon:        b.Daemon,
170
+		Engine:        b.Engine,
171
+		Config:        &newConfig,
172
+		OutStream:     ioutil.Discard,
173
+		ErrStream:     ioutil.Discard,
174
+		disableCommit: true,
175
+	}
176
+
177
+	for i, n := range ast.Children {
178
+		if err := builder.dispatch(i, n); err != nil {
179
+			return job.Error(err)
180
+		}
181
+	}
182
+
183
+	if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil {
184
+		return job.Error(err)
185
+	}
186
+	return engine.StatusOK
187
+}
... ...
@@ -1,6 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"encoding/json"
6
+
4 7
 	"github.com/docker/docker/engine"
5 8
 	"github.com/docker/docker/image"
6 9
 	"github.com/docker/docker/runconfig"
... ...
@@ -18,11 +21,21 @@ func (daemon *Daemon) ContainerCommit(job *engine.Job) engine.Status {
18 18
 	}
19 19
 
20 20
 	var (
21
-		config    = container.Config
22
-		newConfig runconfig.Config
21
+		config       = container.Config
22
+		stdoutBuffer = bytes.NewBuffer(nil)
23
+		newConfig    runconfig.Config
23 24
 	)
24 25
 
25
-	if err := job.GetenvJson("config", &newConfig); err != nil {
26
+	buildConfigJob := daemon.eng.Job("build_config")
27
+	buildConfigJob.Stdout.Add(stdoutBuffer)
28
+	buildConfigJob.Setenv("changes", job.Getenv("changes"))
29
+	// FIXME this should be remove when we remove deprecated config param
30
+	buildConfigJob.Setenv("config", job.Getenv("config"))
31
+
32
+	if err := buildConfigJob.Run(); err != nil {
33
+		return job.Error(err)
34
+	}
35
+	if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil {
26 36
 		return job.Error(err)
27 37
 	}
28 38
 
... ...
@@ -8,6 +8,7 @@ docker-commit - Create a new image from a container's changes
8 8
 **docker commit**
9 9
 [**-a**|**--author**[=*AUTHOR*]]
10 10
 [**--help**]
11
+[**-c**|**--change**[= []**]]
11 12
 [**-m**|**--message**[=*MESSAGE*]]
12 13
 [**-p**|**--pause**[=*true*]]
13 14
 CONTAINER [REPOSITORY[:TAG]]
... ...
@@ -19,6 +20,10 @@ Using an existing container's name or ID you can create a new image.
19 19
 **-a**, **--author**=""
20 20
    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
21 21
 
22
+**-c** , **--change**=[]
23
+   Apply specified Dockerfile instructions while committing the image
24
+   Supported Dockerfile instructions: CMD, ENTRYPOINT, ENV, EXPOSE, ONBUILD, USER, VOLUME, WORKDIR
25
+
22 26
 **--help**
23 27
   Print usage statement
24 28
 
... ...
@@ -38,8 +43,17 @@ create a new image run docker ps to find the container's ID and then run:
38 38
     # docker commit -m="Added Apache to Fedora base image" \
39 39
       -a="A D Ministrator" 98bd7fc99854 fedora/fedora_httpd:20
40 40
 
41
+## Apply specified Dockerfile instructions while committing the image
42
+If an existing container was created without the DEBUG environment
43
+variable set to "true", you can create a new image based on that
44
+container by first getting the container's ID with docker ps and
45
+then running:
46
+
47
+    # docker commit -c="ENV DEBUG true" 98bd7fc99854 debug-image
48
+
41 49
 # HISTORY
42 50
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
43 51
 based on docker.com source material and in
44 52
 June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
45 53
 July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
54
+Oct 2014, updated by Daniel, Dao Quang Minh <daniel at nitrous dot io>
... ...
@@ -6,9 +6,15 @@ docker-import - Create an empty filesystem image and import the contents of the
6 6
 
7 7
 # SYNOPSIS
8 8
 **docker import**
9
+[**-c**|**--change**[= []**]]
9 10
 [**--help**]
10 11
 URL|- [REPOSITORY[:TAG]]
11 12
 
13
+# OPTIONS
14
+**-c**, **--change**=[]
15
+   Apply specified Dockerfile instructions while importing the image
16
+   Supported Dockerfile instructions: CMD, ENTRYPOINT, ENV, EXPOSE, ONBUILD, USER, VOLUME, WORKDIR
17
+
12 18
 # DESCRIPTION
13 19
 Create a new filesystem image from the contents of a tarball (`.tar`,
14 20
 `.tar.gz`, `.tgz`, `.bzip`, `.tar.xz`, `.txz`) into it, then optionally tag it.
... ...
@@ -39,6 +45,11 @@ Import to docker via pipe and stdin:
39 39
 
40 40
     # tar -c . | docker import - exampleimagedir
41 41
 
42
+## Apply specified Dockerfile instructions while importing the image
43
+This example sets the docker image ENV variable DEBUG to true by default.
44
+
45
+    # tar -c . | docker import -c="ENV DEBUG true" - exampleimagedir
46
+
42 47
 # HISTORY
43 48
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
44 49
 based on docker.com source material and internal work.
... ...
@@ -694,6 +694,7 @@ you refer to it on the command line.
694 694
     Create a new image from a container's changes
695 695
 
696 696
       -a, --author=""     Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
697
+      -c, --change=[]     Apply specified Dockerfile instructions while committing the image
697 698
       -m, --message=""    Commit message
698 699
       -p, --pause=true    Pause container during commit
699 700
 
... ...
@@ -708,7 +709,12 @@ while the image is committed. This reduces the likelihood of
708 708
 encountering data corruption during the process of creating the commit.
709 709
 If this behavior is undesired, set the 'p' option to false.
710 710
 
711
-#### Commit an existing container
711
+The `--change` option will apply `Dockerfile` instructions to the image
712
+that is created.
713
+Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`,
714
+`ONBUILD`, `USER`, `VOLUME`, `WORKDIR`
715
+
716
+#### Commit a container
712 717
 
713 718
     $ sudo docker ps
714 719
     ID                  IMAGE               COMMAND             CREATED             STATUS              PORTS
... ...
@@ -720,6 +726,19 @@ If this behavior is undesired, set the 'p' option to false.
720 720
     REPOSITORY                        TAG                 ID                  CREATED             VIRTUAL SIZE
721 721
     SvenDowideit/testimage            version3            f5283438590d        16 seconds ago      335.7 MB
722 722
 
723
+#### Commit a container with new configurations
724
+
725
+    $ sudo docker ps
726
+    ID                  IMAGE               COMMAND             CREATED             STATUS              PORTS
727
+    c3f279d17e0a        ubuntu:12.04        /bin/bash           7 days ago          Up 25 hours
728
+    197387f1b436        ubuntu:12.04        /bin/bash           7 days ago          Up 25 hours
729
+    $ sudo docker inspect -f "{{ .Config.Env }}" c3f279d17e0a
730
+    [HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin]
731
+    $ sudo docker commit --change "ENV DEBUG true" c3f279d17e0a  SvenDowideit/testimage:version3
732
+    f5283438590d
733
+    $ sudo docker inspect -f "{{ .Config.Env }}" f5283438590d
734
+    [HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DEBUG=true]
735
+
723 736
 ## cp
724 737
 
725 738
 Copy files/folders from a container's filesystem to the host
... ...
@@ -1137,11 +1156,18 @@ NOTE: Docker will warn you if any containers exist that are using these untagged
1137 1137
 	tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then
1138 1138
 	optionally tag it.
1139 1139
 
1140
+      -c, --change=[]     Apply specified Dockerfile instructions while importing the image
1141
+
1140 1142
 URLs must start with `http` and point to a single file archive (.tar,
1141 1143
 .tar.gz, .tgz, .bzip, .tar.xz, or .txz) containing a root filesystem. If
1142 1144
 you would like to import from a local directory or archive, you can use
1143 1145
 the `-` parameter to take the data from `STDIN`.
1144 1146
 
1147
+The `--change` option will apply `Dockerfile` instructions to the image
1148
+that is created.
1149
+Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`,
1150
+`ONBUILD`, `USER`, `VOLUME`, `WORKDIR`
1151
+
1145 1152
 #### Examples
1146 1153
 
1147 1154
 **Import from a remote location:**
... ...
@@ -1160,6 +1186,10 @@ Import to docker via pipe and `STDIN`.
1160 1160
 
1161 1161
     $ sudo tar -c . | sudo docker import - exampleimagedir
1162 1162
 
1163
+**Import from a local directory with new configurations:**
1164
+
1165
+    $ sudo tar -c . | sudo docker import --change "ENV DEBUG true" - exampleimagedir
1166
+
1163 1167
 Note the `sudo` in this example – you must preserve
1164 1168
 the ownership of the files (especially root ownership) during the
1165 1169
 archiving with tar. If you are not root (or the sudo command) when you
... ...
@@ -1,12 +1,15 @@
1 1
 package graph
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"encoding/json"
4 6
 	"net/http"
5 7
 	"net/url"
6 8
 
7 9
 	log "github.com/Sirupsen/logrus"
8 10
 	"github.com/docker/docker/engine"
9 11
 	"github.com/docker/docker/pkg/archive"
12
+	"github.com/docker/docker/runconfig"
10 13
 	"github.com/docker/docker/utils"
11 14
 )
12 15
 
... ...
@@ -15,12 +18,14 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
15 15
 		return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
16 16
 	}
17 17
 	var (
18
-		src     = job.Args[0]
19
-		repo    = job.Args[1]
20
-		tag     string
21
-		sf      = utils.NewStreamFormatter(job.GetenvBool("json"))
22
-		archive archive.ArchiveReader
23
-		resp    *http.Response
18
+		src          = job.Args[0]
19
+		repo         = job.Args[1]
20
+		tag          string
21
+		sf           = utils.NewStreamFormatter(job.GetenvBool("json"))
22
+		archive      archive.ArchiveReader
23
+		resp         *http.Response
24
+		stdoutBuffer = bytes.NewBuffer(nil)
25
+		newConfig    runconfig.Config
24 26
 	)
25 27
 	if len(job.Args) > 2 {
26 28
 		tag = job.Args[2]
... ...
@@ -47,7 +52,21 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
47 47
 		defer progressReader.Close()
48 48
 		archive = progressReader
49 49
 	}
50
-	img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, nil)
50
+
51
+	buildConfigJob := job.Eng.Job("build_config")
52
+	buildConfigJob.Stdout.Add(stdoutBuffer)
53
+	buildConfigJob.Setenv("changes", job.Getenv("changes"))
54
+	// FIXME this should be remove when we remove deprecated config param
55
+	buildConfigJob.Setenv("config", job.Getenv("config"))
56
+
57
+	if err := buildConfigJob.Run(); err != nil {
58
+		return job.Error(err)
59
+	}
60
+	if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil {
61
+		return job.Error(err)
62
+	}
63
+
64
+	img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig)
51 65
 	if err != nil {
52 66
 		return job.Error(err)
53 67
 	}
... ...
@@ -240,3 +240,41 @@ func TestCommitWithHostBindMount(t *testing.T) {
240 240
 
241 241
 	logDone("commit - commit bind mounted file")
242 242
 }
243
+
244
+func TestCommitChange(t *testing.T) {
245
+	defer deleteAllContainers()
246
+
247
+	cmd := exec.Command(dockerBinary, "run", "--name", "test", "busybox", "true")
248
+	if _, err := runCommand(cmd); err != nil {
249
+		t.Fatal(err)
250
+	}
251
+
252
+	cmd = exec.Command(dockerBinary, "commit",
253
+		"--change", "EXPOSE 8080",
254
+		"--change", "ENV DEBUG true",
255
+		"--change", "ENV test 1",
256
+		"test", "test-commit")
257
+	imageId, _, err := runCommandWithOutput(cmd)
258
+	if err != nil {
259
+		t.Fatal(imageId, err)
260
+	}
261
+	imageId = strings.Trim(imageId, "\r\n")
262
+	defer deleteImages(imageId)
263
+
264
+	expected := map[string]string{
265
+		"Config.ExposedPorts": "map[8080/tcp:map[]]",
266
+		"Config.Env":          "[DEBUG=true test=1]",
267
+	}
268
+
269
+	for conf, value := range expected {
270
+		res, err := inspectField(imageId, conf)
271
+		if err != nil {
272
+			t.Errorf("failed to get value %s, error: %s", conf, err)
273
+		}
274
+		if res != value {
275
+			t.Errorf("%s('%s'), expected %s", conf, res, value)
276
+		}
277
+	}
278
+
279
+	logDone("commit - commit --change")
280
+}
... ...
@@ -16,6 +16,7 @@ import (
16 16
 
17 17
 	"github.com/docker/docker/api"
18 18
 	"github.com/docker/docker/api/server"
19
+	"github.com/docker/docker/builder"
19 20
 	"github.com/docker/docker/engine"
20 21
 	"github.com/docker/docker/runconfig"
21 22
 	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
... ...
@@ -158,6 +159,8 @@ func TestGetContainersTop(t *testing.T) {
158 158
 
159 159
 func TestPostCommit(t *testing.T) {
160 160
 	eng := NewTestEngine(t)
161
+	b := &builder.BuilderJob{Engine: eng}
162
+	b.Install()
161 163
 	defer mkDaemonFromEngine(eng, t).Nuke()
162 164
 
163 165
 	// Create a container and remove a file
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"testing"
6 6
 	"time"
7 7
 
8
+	"github.com/docker/docker/builder"
8 9
 	"github.com/docker/docker/engine"
9 10
 )
10 11
 
... ...
@@ -22,6 +23,8 @@ func TestCreateNumberHostname(t *testing.T) {
22 22
 
23 23
 func TestCommit(t *testing.T) {
24 24
 	eng := NewTestEngine(t)
25
+	b := &builder.BuilderJob{Engine: eng}
26
+	b.Install()
25 27
 	defer mkDaemonFromEngine(eng, t).Nuke()
26 28
 
27 29
 	config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"})
... ...
@@ -42,6 +45,8 @@ func TestCommit(t *testing.T) {
42 42
 
43 43
 func TestMergeConfigOnCommit(t *testing.T) {
44 44
 	eng := NewTestEngine(t)
45
+	b := &builder.BuilderJob{Engine: eng}
46
+	b.Install()
45 47
 	runtime := mkDaemonFromEngine(eng, t)
46 48
 	defer runtime.Nuke()
47 49