github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/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/pkg/promise" 16 "github.com/docker/docker/pkg/stdcopy" 17 "github.com/docker/docker/pkg/term" 18 "github.com/docker/engine-api/client" 19 "github.com/docker/engine-api/types" 20 "github.com/docker/engine-api/types/container" 21 "github.com/docker/engine-api/types/network" 22 "github.com/docker/go-connections/nat" 23 "github.com/docker/libcompose/config" 24 "github.com/docker/libcompose/labels" 25 "github.com/docker/libcompose/logger" 26 "github.com/docker/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 // FIXME(vdemeester): remove the nil error here 172 func (c *Container) IsRunning(ctx context.Context) (bool, error) { 173 return c.container.State.Running, nil 174 } 175 176 // Run creates, start and attach to the container based on the image name, 177 // the specified configuration. 178 // It will always create a new container. 179 func (c *Container) Run(ctx context.Context, configOverride *config.ServiceConfig) (int, error) { 180 var ( 181 errCh chan error 182 out, stderr io.Writer 183 in io.ReadCloser 184 ) 185 186 if configOverride.StdinOpen { 187 in = os.Stdin 188 } 189 if configOverride.Tty { 190 out = os.Stdout 191 } 192 if configOverride.Tty { 193 stderr = os.Stderr 194 } 195 196 options := types.ContainerAttachOptions{ 197 Stream: true, 198 Stdin: configOverride.StdinOpen, 199 Stdout: configOverride.Tty, 200 Stderr: configOverride.Tty, 201 } 202 203 resp, err := c.client.ContainerAttach(ctx, c.container.ID, options) 204 if err != nil { 205 return -1, err 206 } 207 208 // set raw terminal 209 inFd, _ := term.GetFdInfo(in) 210 state, err := term.SetRawTerminal(inFd) 211 if err != nil { 212 return -1, err 213 } 214 // restore raw terminal 215 defer term.RestoreTerminal(inFd, state) 216 // holdHijackedConnection (in goroutine) 217 errCh = promise.Go(func() error { 218 return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp) 219 }) 220 221 if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil { 222 return -1, err 223 } 224 225 if err := <-errCh; err != nil { 226 logrus.Debugf("Error hijack: %s", err) 227 return -1, err 228 } 229 230 exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID) 231 if err != nil { 232 return -1, err 233 } 234 235 return exitedContainer.State.ExitCode, nil 236 } 237 238 func holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { 239 var err error 240 receiveStdout := make(chan error, 1) 241 if outputStream != nil || errorStream != nil { 242 go func() { 243 // When TTY is ON, use regular copy 244 if tty && outputStream != nil { 245 _, err = io.Copy(outputStream, resp.Reader) 246 } else { 247 _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader) 248 } 249 logrus.Debugf("[hijack] End of stdout") 250 receiveStdout <- err 251 }() 252 } 253 254 stdinDone := make(chan struct{}) 255 go func() { 256 if inputStream != nil { 257 io.Copy(resp.Conn, inputStream) 258 logrus.Debugf("[hijack] End of stdin") 259 } 260 261 if err := resp.CloseWrite(); err != nil { 262 logrus.Debugf("Couldn't send EOF: %s", err) 263 } 264 close(stdinDone) 265 }() 266 267 select { 268 case err := <-receiveStdout: 269 if err != nil { 270 logrus.Debugf("Error receiveStdout: %s", err) 271 return err 272 } 273 case <-stdinDone: 274 if outputStream != nil || errorStream != nil { 275 if err := <-receiveStdout; err != nil { 276 logrus.Debugf("Error receiveStdout: %s", err) 277 return err 278 } 279 } 280 } 281 282 return nil 283 } 284 285 // Start the specified container with the specified host config 286 func (c *Container) Start(ctx context.Context) error { 287 logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Starting container") 288 if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil { 289 logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Failed to start container") 290 return err 291 } 292 return nil 293 } 294 295 // ID returns the container Id. 296 func (c *Container) ID() (string, error) { 297 return c.container.ID, nil 298 } 299 300 // Name returns the container name. 301 func (c *Container) Name() string { 302 return c.container.Name 303 } 304 305 // Restart restarts the container if existing, does nothing otherwise. 306 func (c *Container) Restart(ctx context.Context, timeout int) error { 307 timeoutDuration := time.Duration(timeout) * time.Second 308 return c.client.ContainerRestart(ctx, c.container.ID, &timeoutDuration) 309 } 310 311 // Log forwards container logs to the project configured logger. 312 func (c *Container) Log(ctx context.Context, l logger.Logger, follow bool) error { 313 info, err := c.client.ContainerInspect(ctx, c.container.ID) 314 if err != nil { 315 return err 316 } 317 318 options := types.ContainerLogsOptions{ 319 ShowStdout: true, 320 ShowStderr: true, 321 Follow: follow, 322 Tail: "all", 323 } 324 responseBody, err := c.client.ContainerLogs(ctx, c.container.ID, options) 325 if err != nil { 326 return err 327 } 328 defer responseBody.Close() 329 330 if info.Config.Tty { 331 _, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody) 332 } else { 333 _, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody) 334 } 335 logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error") 336 337 return err 338 } 339 340 // Port returns the host port the specified port is mapped on. 341 func (c *Container) Port(ctx context.Context, port string) (string, error) { 342 if bindings, ok := c.container.NetworkSettings.Ports[nat.Port(port)]; ok { 343 result := []string{} 344 for _, binding := range bindings { 345 result = append(result, binding.HostIP+":"+binding.HostPort) 346 } 347 348 return strings.Join(result, "\n"), nil 349 } 350 return "", nil 351 } 352 353 // Networks returns the containers network 354 func (c *Container) Networks() (map[string]*network.EndpointSettings, error) { 355 return c.container.NetworkSettings.Networks, nil 356 } 357 358 // Image returns the container image. Depending on the engine version its either 359 // the complete id or the digest reference the image. 360 func (c *Container) Image() string { 361 return c.container.Image 362 } 363 364 // ImageConfig returns the container image stored in the config. It's the 365 // human-readable name of the image. 366 func (c *Container) ImageConfig() string { 367 return c.container.Config.Image 368 } 369 370 // Hash returns the container hash stored as label. 371 func (c *Container) Hash() string { 372 return c.container.Config.Labels[labels.HASH.Str()] 373 } 374 375 // Number returns the container number stored as label. 376 func (c *Container) Number() (int, error) { 377 numberStr := c.container.Config.Labels[labels.NUMBER.Str()] 378 return strconv.Atoi(numberStr) 379 }