github.com/xiaobinqt/libcompose@v1.1.0/docker/container/container.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"math"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"golang.org/x/net/context"
    13  
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/api/types/network"
    17  	"github.com/docker/docker/client"
    18  	"github.com/docker/docker/pkg/stdcopy"
    19  	"github.com/docker/docker/pkg/term"
    20  	"github.com/docker/go-connections/nat"
    21  	"github.com/sirupsen/logrus"
    22  	"github.com/xiaobinqt/libcompose/config"
    23  	"github.com/xiaobinqt/libcompose/labels"
    24  	"github.com/xiaobinqt/libcompose/logger"
    25  	"github.com/xiaobinqt/libcompose/project"
    26  )
    27  
    28  // Container holds information about a docker container and the service it is tied on.
    29  type Container struct {
    30  	client    client.ContainerAPIClient
    31  	id        string
    32  	container *types.ContainerJSON
    33  }
    34  
    35  // Create creates a container and return a Container struct (and an error if any)
    36  func Create(ctx context.Context, client client.ContainerAPIClient, name string, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) (*Container, error) {
    37  	container, err := client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return New(ctx, client, container.ID)
    42  }
    43  
    44  // New creates a container struct with the specified client, id and name
    45  func New(ctx context.Context, client client.ContainerAPIClient, id string) (*Container, error) {
    46  	container, err := Get(ctx, client, id)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	return &Container{
    51  		client:    client,
    52  		id:        id,
    53  		container: container,
    54  	}, nil
    55  }
    56  
    57  // NewInspected creates a container struct from an inspected container
    58  func NewInspected(client client.ContainerAPIClient, container *types.ContainerJSON) *Container {
    59  	return &Container{
    60  		client:    client,
    61  		id:        container.ID,
    62  		container: container,
    63  	}
    64  }
    65  
    66  // Info returns info about the container, like name, command, state or ports.
    67  func (c *Container) Info(ctx context.Context) (project.Info, error) {
    68  	infos, err := ListByFilter(ctx, c.client, map[string][]string{
    69  		"name": {c.container.Name},
    70  	})
    71  	if err != nil || len(infos) == 0 {
    72  		return nil, err
    73  	}
    74  	info := infos[0]
    75  
    76  	result := project.Info{}
    77  	result["Id"] = c.container.ID
    78  	result["Name"] = name(info.Names)
    79  	result["Command"] = info.Command
    80  	result["State"] = info.Status
    81  	result["Ports"] = portString(info.Ports)
    82  
    83  	return result, nil
    84  }
    85  
    86  func portString(ports []types.Port) string {
    87  	result := []string{}
    88  
    89  	for _, port := range ports {
    90  		if port.PublicPort > 0 {
    91  			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
    92  		} else {
    93  			result = append(result, fmt.Sprintf("%d/%s", port.PrivatePort, port.Type))
    94  		}
    95  	}
    96  
    97  	return strings.Join(result, ", ")
    98  }
    99  
   100  func name(names []string) string {
   101  	max := math.MaxInt32
   102  	var current string
   103  
   104  	for _, v := range names {
   105  		if len(v) < max {
   106  			max = len(v)
   107  			current = v
   108  		}
   109  	}
   110  
   111  	return current[1:]
   112  }
   113  
   114  // Rename rename the container.
   115  func (c *Container) Rename(ctx context.Context, newName string) error {
   116  	return c.client.ContainerRename(ctx, c.container.ID, newName)
   117  }
   118  
   119  // Remove removes the container.
   120  func (c *Container) Remove(ctx context.Context, removeVolume bool) error {
   121  	return c.client.ContainerRemove(ctx, c.container.ID, types.ContainerRemoveOptions{
   122  		Force:         true,
   123  		RemoveVolumes: removeVolume,
   124  	})
   125  }
   126  
   127  // Stop stops the container.
   128  func (c *Container) Stop(ctx context.Context, timeout int) error {
   129  	timeoutDuration := time.Duration(timeout) * time.Second
   130  	return c.client.ContainerStop(ctx, c.container.ID, &timeoutDuration)
   131  }
   132  
   133  // Pause pauses the container. If the containers are already paused, don't fail.
   134  func (c *Container) Pause(ctx context.Context) error {
   135  	if !c.container.State.Paused {
   136  		if err := c.client.ContainerPause(ctx, c.container.ID); err != nil {
   137  			return err
   138  		}
   139  		return c.updateInnerContainer(ctx)
   140  	}
   141  	return nil
   142  }
   143  
   144  // Unpause unpauses the container. If the containers are not paused, don't fail.
   145  func (c *Container) Unpause(ctx context.Context) error {
   146  	if c.container.State.Paused {
   147  		if err := c.client.ContainerUnpause(ctx, c.container.ID); err != nil {
   148  			return err
   149  		}
   150  		return c.updateInnerContainer(ctx)
   151  	}
   152  	return nil
   153  }
   154  
   155  func (c *Container) updateInnerContainer(ctx context.Context) error {
   156  	container, err := Get(ctx, c.client, c.container.ID)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	c.container = container
   161  	return nil
   162  }
   163  
   164  // Kill kill the container.
   165  func (c *Container) Kill(ctx context.Context, signal string) error {
   166  	return c.client.ContainerKill(ctx, c.container.ID, signal)
   167  }
   168  
   169  // IsRunning returns the running state of the container.
   170  func (c *Container) IsRunning(ctx context.Context) bool {
   171  	return c.container.State.Running
   172  }
   173  
   174  // Run creates, start and attach to the container based on the image name,
   175  // the specified configuration.
   176  // It will always create a new container.
   177  func (c *Container) Run(ctx context.Context, configOverride *config.ServiceConfig) (int, error) {
   178  	var (
   179  		errCh       chan error
   180  		out, stderr io.Writer
   181  		in          io.ReadCloser
   182  		inFd        uintptr
   183  	)
   184  
   185  	if configOverride.StdinOpen {
   186  		in = os.Stdin
   187  	}
   188  	if configOverride.Tty {
   189  		out = os.Stdout
   190  		stderr = os.Stderr
   191  	}
   192  
   193  	options := types.ContainerAttachOptions{
   194  		Stream: true,
   195  		Stdin:  configOverride.StdinOpen,
   196  		Stdout: configOverride.Tty,
   197  		Stderr: configOverride.Tty,
   198  	}
   199  
   200  	resp, err := c.client.ContainerAttach(ctx, c.container.ID, options)
   201  	if err != nil {
   202  		return -1, err
   203  	}
   204  
   205  	if configOverride.StdinOpen {
   206  		// set raw terminal
   207  		inFd, _ = term.GetFdInfo(in)
   208  		state, err := term.SetRawTerminal(inFd)
   209  		if err != nil {
   210  			return -1, err
   211  		}
   212  		// restore raw terminal
   213  		defer term.RestoreTerminal(inFd, state)
   214  	}
   215  
   216  	// holdHijackedConnection (in goroutine)
   217  	errCh = make(chan error, 1)
   218  	go func() {
   219  		errCh <- holdHijackedConnection(configOverride.Tty, in, out, stderr, resp)
   220  	}()
   221  
   222  	if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil {
   223  		return -1, err
   224  	}
   225  
   226  	if configOverride.Tty {
   227  		ws, err := term.GetWinsize(inFd)
   228  		if err != nil {
   229  			return -1, err
   230  		}
   231  
   232  		resizeOpts := types.ResizeOptions{
   233  			Height: uint(ws.Height),
   234  			Width:  uint(ws.Width),
   235  		}
   236  
   237  		if err := c.client.ContainerResize(ctx, c.container.ID, resizeOpts); err != nil {
   238  			return -1, err
   239  		}
   240  	}
   241  
   242  	if err := <-errCh; err != nil {
   243  		logrus.Debugf("Error hijack: %s", err)
   244  		return -1, err
   245  	}
   246  
   247  	exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID)
   248  	if err != nil {
   249  		return -1, err
   250  	}
   251  
   252  	return exitedContainer.State.ExitCode, nil
   253  }
   254  
   255  func holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
   256  	var err error
   257  	receiveStdout := make(chan error, 1)
   258  	if outputStream != nil || errorStream != nil {
   259  		go func() {
   260  			// When TTY is ON, use regular copy
   261  			if tty && outputStream != nil {
   262  				_, err = io.Copy(outputStream, resp.Reader)
   263  			} else {
   264  				_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
   265  			}
   266  			logrus.Debugf("[hijack] End of stdout")
   267  			receiveStdout <- err
   268  		}()
   269  	}
   270  
   271  	stdinDone := make(chan struct{})
   272  	go func() {
   273  		if inputStream != nil {
   274  			io.Copy(resp.Conn, inputStream)
   275  			logrus.Debugf("[hijack] End of stdin")
   276  		}
   277  
   278  		if err := resp.CloseWrite(); err != nil {
   279  			logrus.Debugf("Couldn't send EOF: %s", err)
   280  		}
   281  		close(stdinDone)
   282  	}()
   283  
   284  	select {
   285  	case err := <-receiveStdout:
   286  		if err != nil {
   287  			logrus.Debugf("Error receiveStdout: %s", err)
   288  			return err
   289  		}
   290  	case <-stdinDone:
   291  		if outputStream != nil || errorStream != nil {
   292  			if err := <-receiveStdout; err != nil {
   293  				logrus.Debugf("Error receiveStdout: %s", err)
   294  				return err
   295  			}
   296  		}
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  // Start the specified container with the specified host config
   303  func (c *Container) Start(ctx context.Context) error {
   304  	logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Starting container")
   305  	if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil {
   306  		logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Failed to start container")
   307  		return err
   308  	}
   309  	return nil
   310  }
   311  
   312  // Restart restarts the container if existing, does nothing otherwise.
   313  func (c *Container) Restart(ctx context.Context, timeout int) error {
   314  	timeoutDuration := time.Duration(timeout) * time.Second
   315  	return c.client.ContainerRestart(ctx, c.container.ID, &timeoutDuration)
   316  }
   317  
   318  // Log forwards container logs to the project configured logger.
   319  func (c *Container) Log(ctx context.Context, l logger.Logger, follow bool) error {
   320  	info, err := c.client.ContainerInspect(ctx, c.container.ID)
   321  	if err != nil {
   322  		return err
   323  	}
   324  
   325  	options := types.ContainerLogsOptions{
   326  		ShowStdout: true,
   327  		ShowStderr: true,
   328  		Follow:     follow,
   329  		Tail:       "all",
   330  	}
   331  	responseBody, err := c.client.ContainerLogs(ctx, c.container.ID, options)
   332  	if err != nil {
   333  		return err
   334  	}
   335  	defer responseBody.Close()
   336  
   337  	if info.Config.Tty {
   338  		_, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody)
   339  	} else {
   340  		_, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody)
   341  	}
   342  	logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error")
   343  
   344  	return err
   345  }
   346  
   347  // Port returns the host port the specified port is mapped on.
   348  func (c *Container) Port(ctx context.Context, port string) (string, error) {
   349  	if bindings, ok := c.container.NetworkSettings.Ports[nat.Port(port)]; ok {
   350  		result := []string{}
   351  		for _, binding := range bindings {
   352  			result = append(result, binding.HostIP+":"+binding.HostPort)
   353  		}
   354  
   355  		return strings.Join(result, "\n"), nil
   356  	}
   357  	return "", nil
   358  }
   359  
   360  // Networks returns the containers network
   361  func (c *Container) Networks() (map[string]*network.EndpointSettings, error) {
   362  	return c.container.NetworkSettings.Networks, nil
   363  }
   364  
   365  // ID returns the container Id.
   366  func (c *Container) ID() string {
   367  	return c.container.ID
   368  }
   369  
   370  // ShortID return the container Id in its short form
   371  func (c *Container) ShortID() string {
   372  	return c.container.ID[:12]
   373  }
   374  
   375  // Name returns the container name.
   376  func (c *Container) Name() string {
   377  	return c.container.Name
   378  }
   379  
   380  // Image returns the container image. Depending on the engine version its either
   381  // the complete id or the digest reference the image.
   382  func (c *Container) Image() string {
   383  	return c.container.Image
   384  }
   385  
   386  // ImageConfig returns the container image stored in the config. It's the
   387  // human-readable name of the image.
   388  func (c *Container) ImageConfig() string {
   389  	return c.container.Config.Image
   390  }
   391  
   392  // Hash returns the container hash stored as label.
   393  func (c *Container) Hash() string {
   394  	return c.container.Config.Labels[labels.HASH.Str()]
   395  }
   396  
   397  // Number returns the container number stored as label.
   398  func (c *Container) Number() (int, error) {
   399  	numberStr := c.container.Config.Labels[labels.NUMBER.Str()]
   400  	return strconv.Atoi(numberStr)
   401  }