Browse code

Add an option to set the working directory.

This makes it possible to simply wrap a command inside a container. This makes
it easier to use a container as an unified build environment.

Examples:

~/workspace/docker
$ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu ls
AUTHORS Makefile archive.go changes.go docker
[...]


docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
/home/marco/workspace/docker

Marco Hennings authored on 2013/08/16 03:38:17
Showing 5 changed files
... ...
@@ -90,6 +90,69 @@ func TestRunHostname(t *testing.T) {
90 90
 
91 91
 }
92 92
 
93
+// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
94
+func TestRunWorkdir(t *testing.T) {
95
+	stdout, stdoutPipe := io.Pipe()
96
+
97
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
98
+	defer cleanup(globalRuntime)
99
+
100
+	c := make(chan struct{})
101
+	go func() {
102
+		defer close(c)
103
+		if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
104
+			t.Fatal(err)
105
+		}
106
+	}()
107
+
108
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
109
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
110
+		if err != nil {
111
+			t.Fatal(err)
112
+		}
113
+		if cmdOutput != "/foo/bar\n" {
114
+			t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
115
+		}
116
+	})
117
+
118
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
119
+		<-c
120
+	})
121
+
122
+}
123
+
124
+// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
125
+func TestRunWorkdirExists(t *testing.T) {
126
+	stdout, stdoutPipe := io.Pipe()
127
+
128
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
129
+	defer cleanup(globalRuntime)
130
+
131
+	c := make(chan struct{})
132
+	go func() {
133
+		defer close(c)
134
+		if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
135
+			t.Fatal(err)
136
+		}
137
+	}()
138
+
139
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
140
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
141
+		if err != nil {
142
+			t.Fatal(err)
143
+		}
144
+		if cmdOutput != "/proc\n" {
145
+			t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
146
+		}
147
+	})
148
+
149
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
150
+		<-c
151
+	})
152
+
153
+}
154
+
155
+
93 156
 func TestRunExit(t *testing.T) {
94 157
 	stdin, stdinPipe := io.Pipe()
95 158
 	stdout, stdoutPipe := io.Pipe()
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"errors"
5 6
 	"flag"
6 7
 	"fmt"
7 8
 	"github.com/dotcloud/docker/term"
... ...
@@ -76,6 +77,7 @@ type Config struct {
76 76
 	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
77 77
 	Volumes         map[string]struct{}
78 78
 	VolumesFrom     string
79
+	WorkingDir      string
79 80
 	Entrypoint      []string
80 81
 	NetworkDisabled bool
81 82
 	Privileged      bool
... ...
@@ -92,6 +94,10 @@ type BindMap struct {
92 92
 	Mode    string
93 93
 }
94 94
 
95
+var (
96
+	ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
97
+)
98
+
95 99
 func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
96 100
 	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
97 101
 	if len(args) > 0 && args[0] != "--help" {
... ...
@@ -100,6 +106,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
100 100
 	}
101 101
 
102 102
 	flHostname := cmd.String("h", "", "Container host name")
103
+	flWorkingDir := cmd.String("w", "", "Working directory inside the container")
103 104
 	flUser := cmd.String("u", "", "Username or UID")
104 105
 	flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
105 106
 	flAttach := NewAttachOpts()
... ...
@@ -139,6 +146,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
139 139
 	if *flDetach && len(flAttach) > 0 {
140 140
 		return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
141 141
 	}
142
+	if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
143
+		return nil, nil, cmd, ErrInvaidWorikingDirectory
144
+	}
142 145
 	// If neither -d or -a are set, attach to everything by default
143 146
 	if len(flAttach) == 0 && !*flDetach {
144 147
 		if !*flDetach {
... ...
@@ -197,6 +207,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
197 197
 		VolumesFrom:     *flVolumesFrom,
198 198
 		Entrypoint:      entrypoint,
199 199
 		Privileged:      *flPrivileged,
200
+		WorkingDir:      *flWorkingDir,
200 201
 	}
201 202
 	hostConfig := &HostConfig{
202 203
 		Binds:           binds,
... ...
@@ -666,6 +677,18 @@ func (container *Container) Start(hostConfig *HostConfig) error {
666 666
 		"-e", "container=lxc",
667 667
 		"-e", "HOSTNAME="+container.Config.Hostname,
668 668
 	)
669
+	if container.Config.WorkingDir != "" {
670
+		workingDir := path.Clean(container.Config.WorkingDir)
671
+		utils.Debugf("[working dir] working dir is %s", workingDir)
672
+
673
+		if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
674
+			return nil
675
+		}
676
+
677
+		params = append(params,
678
+			"-w", workingDir,
679
+		)
680
+	}
669 681
 
670 682
 	for _, elem := range container.Config.Env {
671 683
 		params = append(params, "-e", elem)
... ...
@@ -129,7 +129,9 @@ Create a container
129 129
 		"Dns":null,
130 130
 		"Image":"base",
131 131
 		"Volumes":{},
132
-		"VolumesFrom":""
132
+		"VolumesFrom":"",
133
+		"WorkingDir":""
134
+
133 135
 	   }
134 136
 	   
135 137
 	**Example response**:
... ...
@@ -195,7 +197,9 @@ Inspect a container
195 195
 				"Dns": null,
196 196
 				"Image": "base",
197 197
 				"Volumes": {},
198
-				"VolumesFrom": ""
198
+				"VolumesFrom": "",
199
+				"WorkingDir":""
200
+
199 201
 			},
200 202
 			"State": {
201 203
 				"Running": false,
... ...
@@ -746,7 +750,8 @@ Inspect an image
746 746
 				,"Dns":null,
747 747
 				"Image":"base",
748 748
 				"Volumes":null,
749
-				"VolumesFrom":""
749
+				"VolumesFrom":"",
750
+				"WorkingDir":""
750 751
 			},
751 752
 		"Size": 6824592
752 753
 	   }
... ...
@@ -29,6 +29,7 @@
29 29
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
30 30
       -volumes-from="": Mount all volumes from the given container.
31 31
       -entrypoint="": Overwrite the default entrypoint set by the image.
32
+      -w="": Working directory inside the container
32 33
 
33 34
 
34 35
 Examples
... ...
@@ -62,3 +63,22 @@ cgroup controller. In other words, the container can then do almost
62 62
 everything that the host can do. This flag exists to allow special
63 63
 use-cases, like running Docker within Docker.
64 64
 
65
+.. code-block:: bash
66
+
67
+   docker  run -w /path/to/dir/ -i -t  ubuntu pwd
68
+
69
+The ``-w`` lets the command beeing executed inside directory given, 
70
+here /path/to/dir/. If the path does not exists it is created inside the 
71
+container.
72
+
73
+.. code-block:: bash
74
+
75
+   docker  run  -v `pwd`:`pwd` -w `pwd` -i -t  ubuntu pwd
76
+
77
+The ``-v`` flag mounts the current working directory into the container. 
78
+The ``-w`` lets the command beeing executed inside the current 
79
+working directory, by changeing into the directory to the value
80
+returned by ``pwd``. So this combination executes the command
81
+using the container, but inside the current working directory.
82
+
83
+
... ...
@@ -22,6 +22,15 @@ func setupNetworking(gw string) {
22 22
 	}
23 23
 }
24 24
 
25
+// Setup working directory
26
+func setupWorkingDirectory(workdir string) {
27
+	if workdir == "" {
28
+		return
29
+	}
30
+    syscall.Chdir(workdir)
31
+}
32
+
33
+
25 34
 // Takes care of dropping privileges to the desired user
26 35
 func changeUser(u string) {
27 36
 	if u == "" {
... ...
@@ -83,6 +92,7 @@ func SysInit() {
83 83
 	}
84 84
 	var u = flag.String("u", "", "username or uid")
85 85
 	var gw = flag.String("g", "", "gateway address")
86
+	var workdir = flag.String("w", "", "workdir")
86 87
 
87 88
 	var flEnv ListOpts
88 89
 	flag.Var(&flEnv, "e", "Set environment variables")
... ...
@@ -91,6 +101,7 @@ func SysInit() {
91 91
 
92 92
 	cleanupEnv(flEnv)
93 93
 	setupNetworking(*gw)
94
+	setupWorkingDirectory(*workdir)
94 95
 	changeUser(*u)
95 96
 	executeProgram(flag.Arg(0), flag.Args())
96 97
 }