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