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 }