From 1cfa453f4076beb9ad3adb4ff0f3e13737168b18 Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sun, 28 Apr 2019 15:40:31 +0200 Subject: [PATCH] feature/112: implement include-stopped --- app/app.go | 10 +++- container/client.go | 71 +++++++++++++++++++++------- container/container.go | 7 +++ container/mocks/ApiServer.go | 7 ++- container/mocks/data/containers.json | 2 +- main.go | 5 +- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/app/app.go b/app/app.go index 5218d5d..3757088 100644 --- a/app/app.go +++ b/app/app.go @@ -155,8 +155,14 @@ func SetupCliFlags(app *cli.App) { EnvVar: "WATCHTOWER_MONITOR_ONLY", }, cli.BoolFlag{ - Name: "run-once", - Usage: "Run once now and exit", + Name: "run-once", + Usage: "Run once now and exit", + EnvVar: "WATCHTOWER_RUN_ONCE", + }, + cli.BoolFlag{ + Name: "include-stopped", + Usage: "Will also include stopped or exited containers", + EnvVar: "WATCHTOWER_INCLUDE_STOPPED", }, } } diff --git a/container/client.go b/container/client.go index 94f790e..c043640 100644 --- a/container/client.go +++ b/container/client.go @@ -2,6 +2,8 @@ package container import ( "fmt" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "io/ioutil" "time" @@ -33,36 +35,50 @@ type Client interface { // * DOCKER_HOST the docker-engine host to send api requests to // * DOCKER_TLS_VERIFY whether to verify tls certificates // * DOCKER_API_VERSION the minimum docker api version to work with -func NewClient(pullImages bool) Client { +func NewClient(pullImages bool, includeStopped bool) Client { cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv) if err != nil { log.Fatalf("Error instantiating Docker client: %s", err) } - return dockerClient{api: cli, pullImages: pullImages} + return dockerClient{ + api: cli, + pullImages: pullImages, + includeStopped: includeStopped, + } } type dockerClient struct { - api dockerclient.CommonAPIClient - pullImages bool + api dockerclient.CommonAPIClient + pullImages bool + includeStopped bool } func (client dockerClient) ListContainers(fn Filter) ([]Container, error) { cs := []Container{} bg := context.Background() - log.Debug("Retrieving running containers") + log.Info("include stopped: ", client.includeStopped) - runningContainers, err := client.api.ContainerList( + if client.includeStopped { + log.Debug("Retrieving containers including stopped and exited") + } else { + log.Debug("Retrieving running containers") + } + + filter := client.createListFilter() + containers, err := client.api.ContainerList( bg, - types.ContainerListOptions{}) - + types.ContainerListOptions{ + Filters: filter, + }) + log.Info(containers) if err != nil { return nil, err } - for _, runningContainer := range runningContainers { + for _, runningContainer := range containers { containerInfo, err := client.api.ContainerInspect(bg, runningContainer.ID) if err != nil { return nil, err @@ -83,6 +99,18 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) { return cs, nil } +func (client dockerClient) createListFilter() filters.Args { + filterArgs := filters.NewArgs() + filterArgs.Add("status", "running") + + if client.includeStopped { + filterArgs.Add("status", "created") + filterArgs.Add("status", "exited") + } + + return filterArgs +} + func (client dockerClient) StopContainer(c Container, timeout time.Duration) error { bg := context.Background() signal := c.StopSignal() @@ -90,10 +118,11 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err signal = defaultStopSignal } - log.Infof("Stopping %s (%s) with %s", c.Name(), c.ID(), signal) - - if err := client.api.ContainerKill(bg, c.ID(), signal); err != nil { - return err + if c.IsRunning() { + log.Infof("Stopping %s (%s) with %s", c.Name(), c.ID(), signal) + if err := client.api.ContainerKill(bg, c.ID(), signal); err != nil { + return err + } } // Wait for container to exit, but proceed anyway after the timeout elapses @@ -160,15 +189,23 @@ func (client dockerClient) StartContainer(c Container) error { } - log.Debugf("Starting container %s (%s)", name, creation.ID) + return client.startContainerIfPreviouslyRunning(c, creation, bg) - err = client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{}) +} + +func (client dockerClient) startContainerIfPreviouslyRunning(c Container, creation container.ContainerCreateCreatedBody, bg context.Context) error { + name := c.Name() + + if !c.IsRunning() { + return nil + } + + log.Debugf("Starting container %s (%s)", name, creation.ID) + err := client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{}) if err != nil { return err } - return nil - } func (client dockerClient) RenameContainer(c Container, newName string) error { diff --git a/container/container.go b/container/container.go index b32d4aa..66ae505 100644 --- a/container/container.go +++ b/container/container.go @@ -38,6 +38,13 @@ func (c Container) ID() string { return c.containerInfo.ID } +// IsRunning returns a boolean flag indicating whether or not the current +// container is running. The status is determined by the value of the +// container's "State.Running" property. +func (c Container) IsRunning() bool { + return c.containerInfo.State.Running +} + // Name returns the Docker container name. func (c Container) Name() string { return c.containerInfo.Name diff --git a/container/mocks/ApiServer.go b/container/mocks/ApiServer.go index 79290a2..82e05de 100644 --- a/container/mocks/ApiServer.go +++ b/container/mocks/ApiServer.go @@ -18,7 +18,11 @@ func NewMockAPIServer() *httptest.Server { logrus.Debug("Mock server has received a HTTP call on ", r.URL) var response = "" - if isRequestFor("containers/json?limit=0", r) { + if isRequestFor("filters=%7B%22status%22%3A%7B%22running%22%3Atrue%7D%7D&limit=0", r) { + response = getMockJSONFromDisk("./mocks/data/containers.json") + } else if isRequestFor("filters=%7B%22status%22%3A%7B%22created%22%3Atrue%2C%22exited%22%3Atrue%2C%22running%22%3Atrue%7D%7D&limit=0", r) { + response = getMockJSONFromDisk("./mocks/data/containers.json") + } else if isRequestFor("containers/json?limit=0", r) { response = getMockJSONFromDisk("./mocks/data/containers.json") } else if isRequestFor("ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", r) { response = getMockJSONFromDisk("./mocks/data/container_stopped.json") @@ -48,4 +52,3 @@ func getMockJSONFromDisk(relPath string) string { } return string(buf) } - diff --git a/container/mocks/data/containers.json b/container/mocks/data/containers.json index a40cbf3..e2507bf 100644 --- a/container/mocks/data/containers.json +++ b/container/mocks/data/containers.json @@ -12,7 +12,7 @@ "Labels": { "com.centurylinklabs.watchtower": "true" }, - "State": "exited", + "State": "running", "Status": "Exited (1) 6 days ago", "HostConfig": { "NetworkMode": "default" diff --git a/main.go b/main.go index a9b35c7..faf35b6 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,10 @@ func before(c *cli.Context) error { return err } - client = container.NewClient(!c.GlobalBool("no-pull")) + client = container.NewClient( + !c.GlobalBool("no-pull"), + c.GlobalBool("include-stopped"), + ) notifier = notifications.NewNotifier(c) return nil