github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/executor/docker_executor.go (about) 1 /* 2 * Copyright (c) 2021 CodeNotary, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package executor 10 11 import ( 12 "archive/tar" 13 "bytes" 14 "context" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "time" 19 20 "github.com/docker/docker/api/types" 21 "github.com/docker/docker/api/types/container" 22 docker "github.com/docker/docker/client" 23 "github.com/docker/docker/errdefs" 24 "github.com/docker/docker/pkg/stdcopy" 25 ) 26 27 // timeout for kill ing the container if it doesn't properly shuts down 28 var timeout = 5 * time.Second 29 30 type DockerExecutor struct { 31 ctx context.Context 32 client *docker.Client 33 contID string 34 } 35 36 // NewDockerExecutor starts a new container and returns an executor for the container 37 func NewDockerExecutor(image string) (Executor, error) { 38 ctx := context.Background() 39 40 dockerClient, _ := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) 41 cont, err := dockerClient.ContainerCreate( 42 ctx, 43 &container.Config{ 44 Image: image, 45 Entrypoint: []string{"/bin/sh"}, 46 AttachStdin: true, 47 Tty: true, 48 OpenStdin: true, 49 }, 50 nil, nil, nil, "") 51 if err != nil { 52 return nil, fmt.Errorf("cannot create container: %w", err) 53 } 54 55 err = dockerClient.ContainerStart(ctx, cont.ID, types.ContainerStartOptions{}) 56 if err != nil { 57 if errdefs.IsInvalidParameter(err) { 58 return nil, fmt.Errorf("cannot use container without shell. Images 'from scratch' are not supported") 59 } 60 return nil, err 61 } 62 63 return DockerExecutor{ 64 ctx: ctx, 65 client: dockerClient, 66 contID: cont.ID, 67 }, nil 68 } 69 70 // Exec executes a command inside the container 71 func (e DockerExecutor) Exec(cmd []string) ([]byte, []byte, int, error) { 72 var stdOut, stdErr bytes.Buffer 73 exec, err := e.client.ContainerExecCreate(e.ctx, e.contID, types.ExecConfig{ 74 Cmd: cmd, 75 AttachStdout: true, 76 AttachStderr: true, 77 }) 78 if err != nil { 79 return nil, nil, 0, err 80 } 81 82 hijacked, err := e.client.ContainerExecAttach(e.ctx, exec.ID, types.ExecStartCheck{}) 83 if err != nil { 84 return nil, nil, 0, err 85 } 86 defer hijacked.Close() 87 88 done := make(chan error) 89 90 go func() { 91 _, err := stdcopy.StdCopy(&stdOut, &stdErr, hijacked.Reader) 92 done <- err 93 }() 94 95 select { 96 case err = <-done: 97 if err != nil { 98 return nil, nil, 0, err 99 } 100 break 101 case <-e.ctx.Done(): 102 return nil, nil, 0, e.ctx.Err() 103 } 104 105 // check exit code 106 res, err := e.client.ContainerExecInspect(e.ctx, exec.ID) 107 if err != nil { 108 return nil, nil, 0, err 109 } 110 111 return stdOut.Bytes(), stdErr.Bytes(), res.ExitCode, nil 112 } 113 114 // Close stops previously started container by sending "exit" to shell - it is much faster than 115 // stopping with container API 116 func (e DockerExecutor) Close() error { 117 hijacked, err := e.client.ContainerAttach(e.ctx, e.contID, types.ContainerAttachOptions{ 118 Stdin: true, 119 Stream: true, 120 }) 121 if err != nil { 122 // force stop 123 fmt.Printf("cannot attach to container: %s", err) 124 return e.client.ContainerStop(e.ctx, e.contID, &timeout) 125 } 126 _, err = hijacked.Conn.Write([]byte("exit\n")) 127 if err != nil { 128 // force stop 129 fmt.Printf("cannot send command to container: %s", err) 130 return e.client.ContainerStop(e.ctx, e.contID, &timeout) 131 } 132 hijacked.Conn.Close() 133 doneCh, errCh := e.client.ContainerWait(e.ctx, e.contID, container.WaitConditionNotRunning) 134 select { 135 case err = <-errCh: 136 return err 137 case <-doneCh: 138 return nil 139 } 140 } 141 142 // Read reads the file from container and returns its content 143 func (e DockerExecutor) ReadFile(path string) ([]byte, error) { 144 reader, _, err := e.client.CopyFromContainer(e.ctx, e.contID, path) 145 if err != nil { 146 return nil, err 147 } 148 defer reader.Close() 149 150 tr := tar.NewReader(reader) 151 _, err = tr.Next() 152 if err != nil { 153 return nil, fmt.Errorf("error processing file from container: %w", err) 154 } 155 156 return ioutil.ReadAll(tr) 157 } 158 159 // ReadDir reads the files from container directory and returns the content as TAR stream 160 func (e DockerExecutor) ReadDir(path string) (io.ReadCloser, error) { 161 reader, _, err := e.client.CopyFromContainer(e.ctx, e.contID, path) 162 return reader, err 163 }