github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/test/dockerutil/exec.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dockerutil 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "time" 22 23 "github.com/docker/docker/api/types" 24 "github.com/docker/docker/pkg/stdcopy" 25 ) 26 27 // ExecOpts holds arguments for Exec calls. 28 type ExecOpts struct { 29 // Env are additional environment variables. 30 Env []string 31 32 // Privileged enables privileged mode. 33 Privileged bool 34 35 // User is the user to use. 36 User string 37 38 // Enables Tty and stdin for the created process. 39 UseTTY bool 40 41 // WorkDir is the working directory of the process. 42 WorkDir string 43 } 44 45 // Exec creates a process inside the container. 46 func (c *Container) Exec(ctx context.Context, opts ExecOpts, args ...string) (string, error) { 47 p, err := c.doExec(ctx, opts, args) 48 if err != nil { 49 return "", err 50 } 51 52 if exitStatus, err := p.WaitExitStatus(ctx); err != nil { 53 return "", err 54 } else if exitStatus != 0 { 55 out, _ := p.Logs() 56 return out, fmt.Errorf("process terminated with status: %d", exitStatus) 57 } 58 59 return p.Logs() 60 } 61 62 // ExecProcess creates a process inside the container and returns a process struct 63 // for the caller to use. 64 func (c *Container) ExecProcess(ctx context.Context, opts ExecOpts, args ...string) (Process, error) { 65 return c.doExec(ctx, opts, args) 66 } 67 68 func (c *Container) doExec(ctx context.Context, r ExecOpts, args []string) (Process, error) { 69 config := c.execConfig(r, args) 70 resp, err := c.client.ContainerExecCreate(ctx, c.id, config) 71 if err != nil { 72 return Process{}, fmt.Errorf("exec create failed with err: %v", err) 73 } 74 75 hijack, err := c.client.ContainerExecAttach(ctx, resp.ID, types.ExecStartCheck{}) 76 if err != nil { 77 return Process{}, fmt.Errorf("exec attach failed with err: %v", err) 78 } 79 80 return Process{ 81 container: c, 82 execid: resp.ID, 83 conn: hijack, 84 }, nil 85 } 86 87 func (c *Container) execConfig(r ExecOpts, cmd []string) types.ExecConfig { 88 env := append(r.Env, fmt.Sprintf("RUNSC_TEST_NAME=%s", c.Name)) 89 return types.ExecConfig{ 90 AttachStdin: r.UseTTY, 91 AttachStderr: true, 92 AttachStdout: true, 93 Cmd: cmd, 94 Privileged: r.Privileged, 95 WorkingDir: r.WorkDir, 96 Env: env, 97 Tty: r.UseTTY, 98 User: r.User, 99 } 100 101 } 102 103 // Process represents a containerized process. 104 type Process struct { 105 container *Container 106 execid string 107 conn types.HijackedResponse 108 } 109 110 // Write writes buf to the process's stdin. 111 func (p *Process) Write(timeout time.Duration, buf []byte) (int, error) { 112 p.conn.Conn.SetDeadline(time.Now().Add(timeout)) 113 return p.conn.Conn.Write(buf) 114 } 115 116 // Read returns process's stdout and stderr. 117 func (p *Process) Read() (string, string, error) { 118 var stdout, stderr bytes.Buffer 119 if err := p.read(&stdout, &stderr); err != nil { 120 return "", "", err 121 } 122 return stdout.String(), stderr.String(), nil 123 } 124 125 // Logs returns combined stdout/stderr from the process. 126 func (p *Process) Logs() (string, error) { 127 var out bytes.Buffer 128 if err := p.read(&out, &out); err != nil { 129 return "", err 130 } 131 return out.String(), nil 132 } 133 134 func (p *Process) read(stdout, stderr *bytes.Buffer) error { 135 _, err := stdcopy.StdCopy(stdout, stderr, p.conn.Reader) 136 return err 137 } 138 139 // ExitCode returns the process's exit code. 140 func (p *Process) ExitCode(ctx context.Context) (int, error) { 141 _, exitCode, err := p.runningExitCode(ctx) 142 return exitCode, err 143 } 144 145 // IsRunning checks if the process is running. 146 func (p *Process) IsRunning(ctx context.Context) (bool, error) { 147 running, _, err := p.runningExitCode(ctx) 148 return running, err 149 } 150 151 // WaitExitStatus until process completes and returns exit status. 152 func (p *Process) WaitExitStatus(ctx context.Context) (int, error) { 153 waitChan := make(chan (int)) 154 errChan := make(chan (error)) 155 156 go func() { 157 for { 158 running, exitcode, err := p.runningExitCode(ctx) 159 if err != nil { 160 errChan <- fmt.Errorf("error waiting process %s: container %v", p.execid, p.container.Name) 161 } 162 if !running { 163 waitChan <- exitcode 164 } 165 time.Sleep(time.Millisecond * 500) 166 } 167 }() 168 169 select { 170 case ws := <-waitChan: 171 return ws, nil 172 case err := <-errChan: 173 return -1, err 174 } 175 } 176 177 // runningExitCode collects if the process is running and the exit code. 178 // The exit code is only valid if the process has exited. 179 func (p *Process) runningExitCode(ctx context.Context) (bool, int, error) { 180 // If execid is not empty, this is a execed process. 181 if p.execid != "" { 182 status, err := p.container.client.ContainerExecInspect(ctx, p.execid) 183 return status.Running, status.ExitCode, err 184 } 185 // else this is the root process. 186 status, err := p.container.Status(ctx) 187 return status.Running, status.ExitCode, err 188 }