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
This commit is contained in:
Marco Hennings 2013-08-15 20:38:17 +02:00
parent 3885ee00c5
commit 687d27ab57
5 changed files with 125 additions and 3 deletions

View file

@ -90,6 +90,69 @@ func TestRunHostname(t *testing.T) {
}
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
func TestRunWorkdir(t *testing.T) {
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
t.Fatal(err)
}
}()
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "/foo/bar\n" {
t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
func TestRunWorkdirExists(t *testing.T) {
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
t.Fatal(err)
}
}()
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "/proc\n" {
t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
func TestRunExit(t *testing.T) {
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()

View file

@ -2,6 +2,7 @@ package docker
import (
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/term"
@ -76,6 +77,7 @@ type Config struct {
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
WorkingDir string
Entrypoint []string
NetworkDisabled bool
Privileged bool
@ -92,6 +94,10 @@ type BindMap struct {
Mode string
}
var (
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
)
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
@ -100,6 +106,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
flHostname := cmd.String("h", "", "Container host name")
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flAttach := NewAttachOpts()
@ -139,6 +146,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvaidWorikingDirectory
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
@ -197,6 +207,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
VolumesFrom: *flVolumesFrom,
Entrypoint: entrypoint,
Privileged: *flPrivileged,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
@ -666,6 +677,18 @@ func (container *Container) Start(hostConfig *HostConfig) error {
"-e", "container=lxc",
"-e", "HOSTNAME="+container.Config.Hostname,
)
if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir)
if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
return nil
}
params = append(params,
"-w", workingDir,
)
}
for _, elem := range container.Config.Env {
params = append(params, "-e", elem)

View file

@ -129,7 +129,9 @@ Create a container
"Dns":null,
"Image":"base",
"Volumes":{},
"VolumesFrom":""
"VolumesFrom":"",
"WorkingDir":""
}
**Example response**:
@ -195,7 +197,9 @@ Inspect a container
"Dns": null,
"Image": "base",
"Volumes": {},
"VolumesFrom": ""
"VolumesFrom": "",
"WorkingDir":""
},
"State": {
"Running": false,
@ -746,7 +750,8 @@ Inspect an image
,"Dns":null,
"Image":"base",
"Volumes":null,
"VolumesFrom":""
"VolumesFrom":"",
"WorkingDir":""
},
"Size": 6824592
}

View file

@ -29,6 +29,7 @@
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
-volumes-from="": Mount all volumes from the given container.
-entrypoint="": Overwrite the default entrypoint set by the image.
-w="": Working directory inside the container
Examples
@ -62,3 +63,22 @@ cgroup controller. In other words, the container can then do almost
everything that the host can do. This flag exists to allow special
use-cases, like running Docker within Docker.
.. code-block:: bash
docker run -w /path/to/dir/ -i -t ubuntu pwd
The ``-w`` lets the command beeing executed inside directory given,
here /path/to/dir/. If the path does not exists it is created inside the
container.
.. code-block:: bash
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
The ``-v`` flag mounts the current working directory into the container.
The ``-w`` lets the command beeing executed inside the current
working directory, by changeing into the directory to the value
returned by ``pwd``. So this combination executes the command
using the container, but inside the current working directory.

View file

@ -22,6 +22,15 @@ func setupNetworking(gw string) {
}
}
// Setup working directory
func setupWorkingDirectory(workdir string) {
if workdir == "" {
return
}
syscall.Chdir(workdir)
}
// Takes care of dropping privileges to the desired user
func changeUser(u string) {
if u == "" {
@ -83,6 +92,7 @@ func SysInit() {
}
var u = flag.String("u", "", "username or uid")
var gw = flag.String("g", "", "gateway address")
var workdir = flag.String("w", "", "workdir")
var flEnv ListOpts
flag.Var(&flEnv, "e", "Set environment variables")
@ -91,6 +101,7 @@ func SysInit() {
cleanupEnv(flEnv)
setupNetworking(*gw)
setupWorkingDirectory(*workdir)
changeUser(*u)
executeProgram(flag.Arg(0), flag.Args())
}