github.com/Equinix-Metal/virtlet@v1.5.2-0.20210807010419-342346535dc5/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  }