github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/tests/e2e/framework/docker_interface.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package framework 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 26 "github.com/docker/docker/pkg/stdcopy" 27 "github.com/docker/engine-api/client" 28 "github.com/docker/engine-api/types" 29 "github.com/docker/engine-api/types/container" 30 "github.com/docker/engine-api/types/filters" 31 "github.com/docker/go-connections/nat" 32 ) 33 34 // DockerContainerInterface is the receiver object for docker container operations 35 type DockerContainerInterface struct { 36 client *client.Client 37 Name string 38 ID string 39 } 40 41 func newDockerContainerInterface(name string) (*DockerContainerInterface, error) { 42 cli, err := client.NewEnvClient() 43 if err != nil { 44 return nil, err 45 } 46 return &DockerContainerInterface{ 47 client: cli, 48 Name: name, 49 }, nil 50 } 51 52 // Run starts new docker container (similar to `docker run`) 53 func (d *DockerContainerInterface) Run(image string, env map[string]string, network string, ports []string, privileged bool, cmd ...string) error { 54 var envLst []string 55 for key, value := range env { 56 envLst = append(envLst, fmt.Sprintf("%s=%s", key, value)) 57 } 58 59 exposedPorts, portBindings, err := nat.ParsePortSpecs(ports) 60 if err != nil { 61 return err 62 } 63 config := &container.Config{ 64 ExposedPorts: exposedPorts, 65 Env: envLst, 66 Image: image, 67 Cmd: cmd, 68 } 69 70 hostConfig := &container.HostConfig{ 71 NetworkMode: container.NetworkMode(network), 72 PortBindings: portBindings, 73 Privileged: privileged, 74 } 75 76 ctx := context.Background() 77 resp, err := d.client.ContainerCreate(ctx, config, hostConfig, nil, d.Name) 78 if err != nil { 79 return err 80 } 81 if err := d.client.ContainerStart(ctx, resp.ID); err != nil { 82 return err 83 } 84 d.ID = resp.ID 85 return nil 86 } 87 88 // PullImage pulls docker image from remote registry 89 func (d *DockerContainerInterface) PullImage(name string) error { 90 out, err := d.client.ImagePull(context.Background(), name, types.ImagePullOptions{}) 91 if err != nil { 92 return err 93 } 94 defer out.Close() 95 _, err = io.Copy(ioutil.Discard, out) 96 if err != nil { 97 return err 98 } 99 return nil 100 } 101 102 // Delete deletes docker container 103 func (d *DockerContainerInterface) Delete() error { 104 id := d.Name 105 if d.ID != "" { 106 id = d.ID 107 } 108 return d.client.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{ 109 RemoveVolumes: true, 110 Force: true, 111 }) 112 } 113 114 // Container returns info for the container associated with method receiver 115 func (d *DockerContainerInterface) Container() (*types.Container, error) { 116 args := filters.NewArgs() 117 var id string 118 if d.ID != "" { 119 args.Add("id", d.ID) 120 id = d.ID 121 } else if d.Name != "" { 122 args.Add("name", d.Name) 123 id = d.Name 124 } else { 125 return nil, nil 126 } 127 containers, err := d.client.ContainerList(context.Background(), types.ContainerListOptions{ 128 All: true, 129 Filter: args, 130 }) 131 if err != nil { 132 return nil, err 133 } 134 if len(containers) < 1 { 135 return nil, fmt.Errorf("Cannot find docker container %s", id) 136 } 137 return &containers[0], nil 138 } 139 140 // Executor returns interface to run commands in docker container 141 func (d *DockerContainerInterface) Executor(privileged bool, user string) Executor { 142 return &DockerContainerExecInterface{ 143 user: user, 144 privileged: privileged, 145 dockerInterface: d, 146 } 147 } 148 149 // DockerContainerExecInterface is the receiver object for commands execution in docker container 150 type DockerContainerExecInterface struct { 151 dockerInterface *DockerContainerInterface 152 user string 153 privileged bool 154 } 155 156 var _ Executor = &DockerContainerExecInterface{} 157 158 // Run executes command in docker container 159 func (n *DockerContainerExecInterface) Run(stdin io.Reader, stdout, stderr io.Writer, command ...string) error { 160 ctx := context.Background() 161 cfg := types.ExecConfig{ 162 AttachStdout: stdout != nil, 163 AttachStderr: stderr != nil, 164 AttachStdin: stdin != nil, 165 Cmd: command, 166 User: n.user, 167 Privileged: n.privileged, 168 } 169 cr, err := n.dockerInterface.client.ContainerExecCreate(ctx, n.dockerInterface.Name, cfg) 170 if err != nil { 171 return err 172 } 173 174 r, err := n.dockerInterface.client.ContainerExecAttach(ctx, cr.ID, cfg) 175 if err != nil { 176 return err 177 } 178 err = containerHandleDataPiping(r, stdin, stdout, stderr) 179 if err != nil { 180 return err 181 } 182 183 info, err := n.dockerInterface.client.ContainerExecInspect(ctx, cr.ID) 184 if err != nil { 185 return err 186 } 187 188 if info.ExitCode != 0 { 189 return CommandError{ExitCode: info.ExitCode} 190 } 191 192 return nil 193 } 194 195 // Close closes the executor 196 func (*DockerContainerExecInterface) Close() error { 197 return nil 198 } 199 200 // Start is a placeholder for fulfilling Executor interface 201 func (*DockerContainerExecInterface) Start(stdin io.Reader, stdout, stderr io.Writer, command ...string) (Command, error) { 202 return nil, errors.New("not implemented") 203 } 204 205 // Logs is a placeholder for fulfilling Executor interface 206 func (*DockerContainerExecInterface) Logs() (string, error) { 207 return "", errors.New("not implemented") 208 } 209 210 func containerHandleDataPiping(resp types.HijackedResponse, inStream io.Reader, outStream, errorStream io.Writer) error { 211 var err error 212 receiveStdout := make(chan error, 1) 213 if outStream != nil || errorStream != nil { 214 go func() { 215 // Copy data from attached container session to both 216 // out and error streams. 217 _, err = stdcopy.StdCopy(outStream, errorStream, resp.Reader) 218 receiveStdout <- err 219 }() 220 } 221 222 stdinDone := make(chan struct{}) 223 go func() { 224 if inStream != nil { 225 io.Copy(resp.Conn, inStream) 226 } 227 228 resp.CloseWrite() 229 close(stdinDone) 230 }() 231 232 select { 233 case err := <-receiveStdout: 234 if err != nil { 235 return err 236 } 237 case <-stdinDone: 238 if outStream != nil || errorStream != nil { 239 if err := <-receiveStdout; err != nil { 240 return err 241 } 242 } 243 } 244 245 return nil 246 }