Add support for docker exec to return cmd exitStatus
Note - only support the non-detached mode of exec right now. Another PR will add -d support. Closes #8703 Signed-off-by: Doug Davis <dug@us.ibm.com>
This commit is contained in:
parent
d7626e97b6
commit
90928eb114
9 changed files with 204 additions and 3 deletions
|
@ -2574,6 +2574,8 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
||||
return err
|
||||
}
|
||||
// For now don't print this - wait for when we support exec wait()
|
||||
// fmt.Fprintf(cli.out, "%s\n", execID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2636,5 +2638,14 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var status int
|
||||
if _, status, err = getExecExitCode(cli, execID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return &utils.StatusError{StatusCode: status}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -234,6 +234,26 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|||
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
||||
}
|
||||
|
||||
// getExecExitCode perform an inspect on the exec command. It returns
|
||||
// the running state and the exit code.
|
||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
return false, -1, err
|
||||
}
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
var result engine.Env
|
||||
if err := result.Decode(stream); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
|
||||
return result.GetBool("Running"), result.GetInt("ExitCode"), nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
|
|
|
@ -956,6 +956,15 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res
|
|||
return job.Run()
|
||||
}
|
||||
|
||||
func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter 'id'")
|
||||
}
|
||||
var job = eng.Job("execInspect", vars["id"])
|
||||
streamJSON(job, w, false)
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
|
@ -1277,6 +1286,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
|||
"/containers/{name:.*}/top": getContainersTop,
|
||||
"/containers/{name:.*}/logs": getContainersLogs,
|
||||
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
||||
"/exec/{id:.*}/json": getExecByID,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
|
|
|
@ -602,6 +602,10 @@ func (container *Container) cleanup() {
|
|||
if err := container.Unmount(); err != nil {
|
||||
log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
||||
}
|
||||
|
||||
for _, eConfig := range container.execCommands.s {
|
||||
container.daemon.unregisterExecCommand(eConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) KillSig(sig int) error {
|
||||
|
|
|
@ -130,6 +130,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
|||
"execCreate": daemon.ContainerExecCreate,
|
||||
"execStart": daemon.ContainerExecStart,
|
||||
"execResize": daemon.ContainerExecResize,
|
||||
"execInspect": daemon.ContainerExecInspect,
|
||||
} {
|
||||
if err := eng.Register(name, method); err != nil {
|
||||
return err
|
||||
|
|
|
@ -24,6 +24,7 @@ type execConfig struct {
|
|||
sync.Mutex
|
||||
ID string
|
||||
Running bool
|
||||
ExitCode int
|
||||
ProcessConfig execdriver.ProcessConfig
|
||||
StreamConfig
|
||||
OpenStdin bool
|
||||
|
@ -207,8 +208,9 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
|||
|
||||
execErr := make(chan error)
|
||||
|
||||
// Remove exec from daemon and container.
|
||||
defer d.unregisterExecCommand(execConfig)
|
||||
// Note, the execConfig data will be removed when the container
|
||||
// itself is deleted. This allows us to query it (for things like
|
||||
// the exitStatus) even after the cmd is done running.
|
||||
|
||||
go func() {
|
||||
err := container.Exec(execConfig)
|
||||
|
@ -231,7 +233,17 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
|||
}
|
||||
|
||||
func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
||||
exitStatus, err := d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
||||
|
||||
// On err, make sure we don't leave ExitCode at zero
|
||||
if err != nil && exitStatus == 0 {
|
||||
exitStatus = 128
|
||||
}
|
||||
|
||||
execConfig.ExitCode = exitStatus
|
||||
execConfig.Running = false
|
||||
|
||||
return exitStatus, err
|
||||
}
|
||||
|
||||
func (container *Container) Exec(execConfig *execConfig) error {
|
||||
|
|
|
@ -64,3 +64,21 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
|
|||
}
|
||||
return job.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) ContainerExecInspect(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 1 {
|
||||
return job.Errorf("usage: %s ID", job.Name)
|
||||
}
|
||||
id := job.Args[0]
|
||||
eConfig, err := daemon.getExecConfig(id)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(*eConfig)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
job.Stdout.Write(b)
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
|
|
@ -1598,6 +1598,114 @@ Status Codes:
|
|||
- **201** – no error
|
||||
- **404** – no such exec instance
|
||||
|
||||
### Exec Inspect
|
||||
|
||||
`GET /exec/(id)/json`
|
||||
|
||||
Return low-level information about the exec command `id`.
|
||||
|
||||
**Example request**:
|
||||
|
||||
GET /exec/11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39/json HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: plain/text
|
||||
|
||||
{
|
||||
"ID" : "11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39",
|
||||
"Running" : false,
|
||||
"ExitCode" : 2,
|
||||
"ProcessConfig" : {
|
||||
"privileged" : false,
|
||||
"user" : "",
|
||||
"tty" : false,
|
||||
"entrypoint" : "sh",
|
||||
"arguments" : [
|
||||
"-c",
|
||||
"exit 2"
|
||||
]
|
||||
},
|
||||
"OpenStdin" : false,
|
||||
"OpenStderr" : false,
|
||||
"OpenStdout" : false,
|
||||
"Container" : {
|
||||
"State" : {
|
||||
"Running" : true,
|
||||
"Paused" : false,
|
||||
"Restarting" : false,
|
||||
"OOMKilled" : false,
|
||||
"Pid" : 3650,
|
||||
"ExitCode" : 0,
|
||||
"Error" : "",
|
||||
"StartedAt" : "2014-11-17T22:26:03.717657531Z",
|
||||
"FinishedAt" : "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"ID" : "8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c",
|
||||
"Created" : "2014-11-17T22:26:03.626304998Z",
|
||||
"Path" : "date",
|
||||
"Args" : [],
|
||||
"Config" : {
|
||||
"Hostname" : "8f177a186b97",
|
||||
"Domainname" : "",
|
||||
"User" : "",
|
||||
"Memory" : 0,
|
||||
"MemorySwap" : 0,
|
||||
"CpuShares" : 0,
|
||||
"Cpuset" : "",
|
||||
"AttachStdin" : false,
|
||||
"AttachStdout" : false,
|
||||
"AttachStderr" : false,
|
||||
"PortSpecs" : null,
|
||||
"ExposedPorts" : null,
|
||||
"Tty" : false,
|
||||
"OpenStdin" : false,
|
||||
"StdinOnce" : false,
|
||||
"Env" : [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ],
|
||||
"Cmd" : [
|
||||
"date"
|
||||
],
|
||||
"Image" : "ubuntu",
|
||||
"Volumes" : null,
|
||||
"WorkingDir" : "",
|
||||
"Entrypoint" : null,
|
||||
"NetworkDisabled" : false,
|
||||
"MacAddress" : "",
|
||||
"OnBuild" : null,
|
||||
"SecurityOpt" : null
|
||||
},
|
||||
"Image" : "5506de2b643be1e6febbf3b8a240760c6843244c41e12aa2f60ccbb7153d17f5",
|
||||
"NetworkSettings" : {
|
||||
"IPAddress" : "172.17.0.2",
|
||||
"IPPrefixLen" : 16,
|
||||
"MacAddress" : "02:42:ac:11:00:02",
|
||||
"Gateway" : "172.17.42.1",
|
||||
"Bridge" : "docker0",
|
||||
"PortMapping" : null,
|
||||
"Ports" : {}
|
||||
},
|
||||
"ResolvConfPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/resolv.conf",
|
||||
"HostnamePath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hostname",
|
||||
"HostsPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hosts",
|
||||
"Name" : "/test",
|
||||
"Driver" : "aufs",
|
||||
"ExecDriver" : "native-0.2",
|
||||
"MountLabel" : "",
|
||||
"ProcessLabel" : "",
|
||||
"AppArmorProfile" : "",
|
||||
"RestartCount" : 0,
|
||||
"Volumes" : {},
|
||||
"VolumesRW" : {}
|
||||
}
|
||||
}
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **200** – no error
|
||||
- **404** – no such exec instance
|
||||
- **500** - server error
|
||||
|
||||
# 3. Going further
|
||||
|
||||
## 3.1 Inside `docker run`
|
||||
|
|
|
@ -213,3 +213,20 @@ func TestExecEnv(t *testing.T) {
|
|||
|
||||
logDone("exec - exec inherits correct env")
|
||||
}
|
||||
|
||||
func TestExecExitStatus(t *testing.T) {
|
||||
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top")
|
||||
if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
|
||||
// Test normal (non-detached) case first
|
||||
cmd := exec.Command(dockerBinary, "exec", "top", "sh", "-c", "exit 23")
|
||||
ec, _ := runCommand(cmd)
|
||||
|
||||
if ec != 23 {
|
||||
t.Fatalf("Should have had an ExitCode of 23, not: %d", ec)
|
||||
}
|
||||
|
||||
logDone("exec - exec non-zero ExitStatus")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue