1
0
mirror of https://github.com/containrrr/watchtower.git synced 2024-12-15 09:14:13 +02:00
watchtower/container/client.go
Brian DeHamer e21c21ec3b Account for latency in container removal
Under certain conditions when watchtower is monitoring a Docker Swarm
cluster there would be cases where an updated container could not be
started because the old hadn't yet been removed (name conflicts, mapped
port conflicts, etc). We suspect that this has something to do with the
async nature of swarm and even though we've asked the swarm master to
remove a container it may not be completely removed from the associated
node. The fix is to do some polling after the remove container call to
ensure that the container is truly gone before proceeding.
2015-07-28 19:29:20 +00:00

168 lines
3.9 KiB
Go

package container
import (
"crypto/tls"
"fmt"
"time"
log "github.com/Sirupsen/logrus"
"github.com/samalba/dockerclient"
)
const (
defaultStopSignal = "SIGTERM"
)
type Filter func(Container) bool
type Client interface {
ListContainers(Filter) ([]Container, error)
StopContainer(Container, time.Duration) error
StartContainer(Container) error
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
}
func NewClient(dockerHost string, tlsConfig *tls.Config, pullImages bool) Client {
docker, err := dockerclient.NewDockerClient(dockerHost, tlsConfig)
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
}
return DockerClient{api: docker, pullImages: pullImages}
}
type DockerClient struct {
api dockerclient.Client
pullImages bool
}
func (client DockerClient) ListContainers(fn Filter) ([]Container, error) {
cs := []Container{}
log.Debug("Retrieving running containers")
runningContainers, err := client.api.ListContainers(false, false, "")
if err != nil {
return nil, err
}
for _, runningContainer := range runningContainers {
log.Debugf("Inspecting container %s (%s)", runningContainer.Names[0], runningContainer.Id)
containerInfo, err := client.api.InspectContainer(runningContainer.Id)
if err != nil {
return nil, err
}
log.Debugf("Inspecting image %s (%s)", containerInfo.Config.Image, containerInfo.Image)
imageInfo, err := client.api.InspectImage(containerInfo.Image)
if err != nil {
return nil, err
}
c := Container{containerInfo: containerInfo, imageInfo: imageInfo}
if fn(c) {
cs = append(cs, c)
}
}
return cs, nil
}
func (client DockerClient) StopContainer(c Container, timeout time.Duration) error {
signal := c.StopSignal()
if signal == "" {
signal = defaultStopSignal
}
log.Infof("Stopping %s (%s) with %s", c.Name(), c.ID(), signal)
if err := client.api.KillContainer(c.ID(), signal); err != nil {
return err
}
// Wait for container to exit, but proceed anyway after the timeout elapses
client.waitForStop(c, timeout)
log.Debugf("Removing container %s", c.ID())
if err := client.api.RemoveContainer(c.ID(), true, false); err != nil {
return err
}
// Wait for container to be removed. In this case an error is a good thing
if err := client.waitForStop(c, timeout); err == nil {
return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
}
return nil
}
func (client DockerClient) StartContainer(c Container) error {
config := c.runtimeConfig()
hostConfig := c.hostConfig()
name := c.Name()
log.Infof("Starting %s", name)
newContainerID, err := client.api.CreateContainer(config, name)
if err != nil {
return err
}
log.Debugf("Starting container %s (%s)", name, newContainerID)
return client.api.StartContainer(newContainerID, hostConfig)
}
func (client DockerClient) RenameContainer(c Container, newName string) error {
log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID(), newName)
return client.api.RenameContainer(c.ID(), newName)
}
func (client DockerClient) IsContainerStale(c Container) (bool, error) {
oldImageInfo := c.imageInfo
imageName := c.ImageName()
if client.pullImages {
log.Debugf("Pulling %s for %s", imageName, c.Name())
if err := client.api.PullImage(imageName, nil); err != nil {
return false, err
}
}
newImageInfo, err := client.api.InspectImage(imageName)
if err != nil {
return false, err
}
if newImageInfo.Id != oldImageInfo.Id {
log.Infof("Found new %s image (%s)", imageName, newImageInfo.Id)
return true, nil
}
return false, nil
}
func (client DockerClient) waitForStop(c Container, waitTime time.Duration) error {
timeout := time.After(waitTime)
for {
select {
case <-timeout:
return nil
default:
if ci, err := client.api.InspectContainer(c.ID()); err != nil {
return err
} else if !ci.State.Running {
return nil
}
time.Sleep(1 * time.Second)
}
}
}