mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Merge pull request #9208 from duglin/Issue8703
Add support for docker exec to return cmd exitStatus
This commit is contained in:
		
						commit
						00c2a8f323
					
				
					 9 changed files with 204 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -2601,6 +2601,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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2663,5 +2665,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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -957,6 +957,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")
 | 
			
		||||
| 
						 | 
				
			
			@ -1281,6 +1290,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
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +206,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)
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +231,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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,3 +65,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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1606,6 +1606,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…
	
	Add table
		Add a link
		
	
		Reference in a new issue