github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/internal/container/exec.go (about) 1 package container 2 3 import ( 4 "bytes" 5 "context" 6 7 "github.com/docker/docker/api/types" 8 "github.com/docker/docker/client" 9 "github.com/docker/docker/pkg/stdcopy" 10 ) 11 12 // ExecResult represents a result returned from Exec() 13 type ExecResult struct { 14 ExitCode int 15 outBuffer *bytes.Buffer 16 errBuffer *bytes.Buffer 17 } 18 19 // Stdout returns stdout output of a command run by Exec() 20 func (res *ExecResult) Stdout() string { 21 return res.outBuffer.String() 22 } 23 24 // Stderr returns stderr output of a command run by Exec() 25 func (res *ExecResult) Stderr() string { 26 return res.errBuffer.String() 27 } 28 29 // Combined returns combined stdout and stderr output of a command run by Exec() 30 func (res *ExecResult) Combined() string { 31 return res.outBuffer.String() + res.errBuffer.String() 32 } 33 34 // Exec executes a command inside a container, returning the result 35 // containing stdout, stderr, and exit code. Note: 36 // - this is a synchronous operation; 37 // - cmd stdin is closed. 38 func Exec(ctx context.Context, cli client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) (ExecResult, error) { 39 // prepare exec 40 execConfig := types.ExecConfig{ 41 AttachStdout: true, 42 AttachStderr: true, 43 Cmd: cmd, 44 } 45 46 for _, op := range ops { 47 op(&execConfig) 48 } 49 50 cresp, err := cli.ContainerExecCreate(ctx, id, execConfig) 51 if err != nil { 52 return ExecResult{}, err 53 } 54 execID := cresp.ID 55 56 // run it, with stdout/stderr attached 57 aresp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{}) 58 if err != nil { 59 return ExecResult{}, err 60 } 61 defer aresp.Close() 62 63 // read the output 64 var outBuf, errBuf bytes.Buffer 65 outputDone := make(chan error, 1) 66 67 go func() { 68 // StdCopy demultiplexes the stream into two buffers 69 _, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader) 70 outputDone <- err 71 }() 72 73 select { 74 case err := <-outputDone: 75 if err != nil { 76 return ExecResult{}, err 77 } 78 break 79 80 case <-ctx.Done(): 81 return ExecResult{}, ctx.Err() 82 } 83 84 // get the exit code 85 iresp, err := cli.ContainerExecInspect(ctx, execID) 86 if err != nil { 87 return ExecResult{}, err 88 } 89 90 return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &outBuf, errBuffer: &errBuf}, nil 91 }