From fe0e34e8577026feac6b3d3a42dcc80a4c190043 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tanguy=20=E2=A7=93=20Herrmann?=
Date: Sat, 17 Dec 2016 22:50:36 +0100
Subject: [PATCH 001/761] Reuse the network config for the relaunch
---
container/client.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/container/client.go b/container/client.go
index cf2e589..86e1719 100644
--- a/container/client.go
+++ b/container/client.go
@@ -8,6 +8,7 @@ import (
log "github.com/Sirupsen/logrus"
dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/network"
"golang.org/x/net/context"
)
@@ -118,10 +119,11 @@ func (client dockerClient) StartContainer(c Container) error {
bg := context.Background();
config := c.runtimeConfig()
hostConfig := c.hostConfig()
+ networkConfig := &network.NetworkingConfig{EndpointsConfig: c.containerInfo.NetworkSettings.Networks}
name := c.Name()
log.Infof("Starting %s", name)
- creation, err := client.api.ContainerCreate(bg, config, hostConfig, nil, name)
+ creation, err := client.api.ContainerCreate(bg, config, hostConfig, networkConfig, name)
if err != nil {
return err
}
From f8a2f80b92da3beae08efd84d2f024cefe738da8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tanguy=20=E2=A7=93=20Herrmann?=
Date: Sat, 17 Dec 2016 22:52:54 +0100
Subject: [PATCH 002/761] Make an updated container connects to all the
previously connected net
With insights from https://github.com/docker/docker/issues/29265
the behaviour is the same as the one from docker-compose
* connect to 1 network (at random) at start
* disconnect from that network
* reconnect to all the network from the previous configuration
---
container/client.go | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/container/client.go b/container/client.go
index 86e1719..8996c09 100644
--- a/container/client.go
+++ b/container/client.go
@@ -120,17 +120,48 @@ func (client dockerClient) StartContainer(c Container) error {
config := c.runtimeConfig()
hostConfig := c.hostConfig()
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.containerInfo.NetworkSettings.Networks}
+ // simpleNetworkConfig is a networkConfig with only 1 network.
+ // see: https://github.com/docker/docker/issues/29265
+ simpleNetworkConfig := func() *network.NetworkingConfig {
+ oneEndpoint := make(map[string]*network.EndpointSettings)
+ for k, v := range networkConfig.EndpointsConfig {
+ oneEndpoint[k] = v
+ // we only need 1
+ break
+ }
+ return &network.NetworkingConfig{EndpointsConfig: oneEndpoint}
+ }()
+
name := c.Name()
log.Infof("Starting %s", name)
- creation, err := client.api.ContainerCreate(bg, config, hostConfig, networkConfig, name)
+ creation, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, name)
if err != nil {
return err
}
log.Debugf("Starting container %s (%s)", name, creation.ID)
- return client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{})
+ err = client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{})
+ if err != nil {
+ return err
+ }
+
+ for k, _ := range simpleNetworkConfig.EndpointsConfig {
+ err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
+ if err != nil {
+ return err
+ }
+ }
+
+ for k, v := range networkConfig.EndpointsConfig {
+ err = client.api.NetworkConnect(bg, k, creation.ID, v)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+
}
func (client dockerClient) RenameContainer(c Container, newName string) error {
From 4909b90662d51c590eef9bc2ba7690464436da85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tanguy=20=E2=A7=93=20Herrmann?=
Date: Sat, 17 Dec 2016 22:55:45 +0100
Subject: [PATCH 003/761] go fmt done!
---
container/client.go | 13 ++++++-------
container/trust.go | 8 ++++----
container/util.go | 4 ----
container/util_test.go | 1 -
4 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/container/client.go b/container/client.go
index 8996c09..233e753 100644
--- a/container/client.go
+++ b/container/client.go
@@ -2,13 +2,13 @@ package container
import (
"fmt"
- "time"
"io/ioutil"
+ "time"
log "github.com/Sirupsen/logrus"
- dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
+ dockerclient "github.com/docker/docker/client"
"golang.org/x/net/context"
)
@@ -77,7 +77,7 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
}
c := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
- if (fn(c)) {
+ if fn(c) {
cs = append(cs, c)
}
}
@@ -116,7 +116,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
}
func (client dockerClient) StartContainer(c Container) error {
- bg := context.Background();
+ bg := context.Background()
config := c.runtimeConfig()
hostConfig := c.hostConfig()
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.containerInfo.NetworkSettings.Networks}
@@ -178,7 +178,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
if client.pullImages {
log.Debugf("Pulling %s for %s", imageName, c.Name())
-
+
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
auth, err := EncodedAuth(imageName)
if err != nil {
@@ -197,7 +197,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return false, err
}
defer response.Close()
-
+
// the pull request will be aborted prematurely unless the response is read
_, err = ioutil.ReadAll(response)
}
@@ -213,7 +213,6 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
} else {
log.Debugf("No new images found for %s", c.Name())
}
-
return false, nil
}
diff --git a/container/trust.go b/container/trust.go
index 6871333..db87d84 100644
--- a/container/trust.go
+++ b/container/trust.go
@@ -2,8 +2,6 @@ package container
import (
"errors"
- "os"
- "strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/reference"
@@ -11,6 +9,8 @@ import (
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/cliconfig/configfile"
"github.com/docker/docker/cliconfig/credentials"
+ "os"
+ "strings"
)
/**
@@ -35,7 +35,7 @@ func EncodedEnvAuth(ref string) (string, error) {
username := os.Getenv("REPO_USER")
password := os.Getenv("REPO_PASS")
if username != "" && password != "" {
- auth := types.AuthConfig {
+ auth := types.AuthConfig{
Username: username,
Password: password,
}
@@ -80,7 +80,7 @@ func ParseServerAddress(ref string) (string, error) {
}
parts := strings.Split(repository, "/")
return parts[0], nil
-
+
}
// CredentialsStore returns a new credentials store based
diff --git a/container/util.go b/container/util.go
index 8947c82..3db01d1 100644
--- a/container/util.go
+++ b/container/util.go
@@ -1,8 +1,5 @@
package container
-import (
-)
-
func sliceEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
@@ -65,4 +62,3 @@ func structMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
return m
}
-
diff --git a/container/util_test.go b/container/util_test.go
index 9ed551d..c4d5ca7 100644
--- a/container/util_test.go
+++ b/container/util_test.go
@@ -63,4 +63,3 @@ func TestStructMapSubtract(t *testing.T) {
assert.Equal(t, map[string]struct{}{"a": x, "b": x, "c": x}, m1)
assert.Equal(t, map[string]struct{}{"a": x, "c": x}, m2)
}
-
From b45fc5a9bad2e4889534a5190acb9f6d9c527e35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tanguy=20=E2=A7=93=20Herrmann?=
Date: Mon, 2 Jan 2017 16:19:51 +0100
Subject: [PATCH 004/761] Fix comment from HoundCI
https://github.com/CenturyLinkLabs/watchtower/pull/40#discussion_r94326156
---
container/client.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/client.go b/container/client.go
index 233e753..3401c1c 100644
--- a/container/client.go
+++ b/container/client.go
@@ -147,7 +147,7 @@ func (client dockerClient) StartContainer(c Container) error {
return err
}
- for k, _ := range simpleNetworkConfig.EndpointsConfig {
+ for k := range simpleNetworkConfig.EndpointsConfig {
err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
if err != nil {
return err
From edfad5b78689be0d5490945db3eb49307d0878bd Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Sat, 7 Jan 2017 00:46:13 +0100
Subject: [PATCH 005/761] RenameContainer implemented, this fixes the problem
that watchtower can't update itself.
---
container/client.go | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/container/client.go b/container/client.go
index cf2e589..62dbd36 100644
--- a/container/client.go
+++ b/container/client.go
@@ -132,10 +132,9 @@ func (client dockerClient) StartContainer(c Container) error {
}
func (client dockerClient) RenameContainer(c Container, newName string) error {
+ bg := context.Background()
log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID(), newName)
- //return client.api.ContainerRename(c.ID(), newName)
- // no op
- return nil
+ return client.api.ContainerRename(bg, c.ID(), newName)
}
func (client dockerClient) IsContainerStale(c Container) (bool, error) {
From 07924aacad8a9b04c3f1c98a859cfd6e1f3a1b57 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 19:06:20 +0100
Subject: [PATCH 006/761] Fixed typo in LICENSE and renamed to md.
---
LICENSE => LICENSE.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename LICENSE => LICENSE.md (99%)
diff --git a/LICENSE b/LICENSE.md
similarity index 99%
rename from LICENSE
rename to LICENSE.md
index 4c81195..3a3284a 100644
--- a/LICENSE
+++ b/LICENSE.md
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2015 Watchtowner contributors
+ Copyright 2015 Watchtower contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
From f1822f9cd2135b8f8e151dce18887b20f7f22f82 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 19:07:25 +0100
Subject: [PATCH 007/761] Renamed centurylink to v2tec.
---
README.md | 26 ++++++++++++--------------
actions/check.go | 2 +-
actions/update.go | 2 +-
container/mockclient/mock.go | 2 +-
container/mockclient/mock_test.go | 2 +-
main.go | 6 +++---
6 files changed, 19 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 6f673cf..0e75902 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,7 @@
# Watchtower
-
-
-[](https://circleci.com/gh/CenturyLinkLabs/watchtower)
-[](https://godoc.org/github.com/CenturyLinkLabs/watchtower)
-[](https://imagelayers.io/?images=centurylink/watchtower:latest 'Get your own badge on imagelayers.io')
+[](https://circleci.com/gh/v2tec/watchtower)
+[](https://godoc.org/github.com/v2tec/watchtower)
+[](https://microbadger.com/images/v2tec/watchtower "Get your own image badge on microbadger.com")
A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.
@@ -19,14 +17,14 @@ For example, let's say you were running watchtower along with an instance of *ce
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
-6cc4d2a9d1a5 centurylink/watchtower Up 15 minutes watchtower
+6cc4d2a9d1a5 v2tec/watchtower Up 15 minutes watchtower
```
Every few mintutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
## Usage
-Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `centurylink/watchtower` image.
+Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `v2tec/watchtower` image.
Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount */var/run/docker.sock* into the container with the -v flag when you run it.
@@ -36,7 +34,7 @@ Run the `watchtower` container with the following command:
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
- centurylink/watchtower
+ v2tec/watchtower
```
If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
@@ -58,7 +56,7 @@ By default, watchtower will monitor all containers running within the Docker dae
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
- centurylink/watchtower nginx redis
+ v2tec/watchtower nginx redis
```
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other running containers will be ignored.
@@ -70,7 +68,7 @@ When no arguments are specified, watchtower will monitor all running containers.
Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string:
```
-docker run --rm centurylink/watchtower --help
+docker run --rm v2tec/watchtower --help
```
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
@@ -112,7 +110,7 @@ By default, watchtower is set-up to monitor the local Docker daemon (the same da
```
docker run -d \
--name watchtower \
- centurylink/watchtower --host "tcp://10.0.1.2:2375"
+ v2tec/watchtower --host "tcp://10.0.1.2:2375"
```
or
@@ -121,7 +119,7 @@ or
docker run -d \
--name watchtower \
-e DOCKER_HOST="tcp://10.0.1.2:2375" \
- centurylink/watchtower
+ v2tec/watchtower
```
Note in both of the examples above that it is unnecessary to mount the */var/run/docker.sock* into the watchtower container.
@@ -139,9 +137,9 @@ docker run -d \
--name watchtower \
-e DOCKER_HOST=$DOCKER_HOST \
-v $DOCKER_CERT_PATH:/etc/ssl/docker \
- centurylink/watchtower --tlsverify
+ v2tec/watchtower --tlsverify
```
## Updating Watchtower
-If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *centurylink/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
+If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *v2tec/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
diff --git a/actions/check.go b/actions/check.go
index e593bcd..8fb5786 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -3,7 +3,7 @@ package actions
import (
"sort"
- "github.com/CenturyLinkLabs/watchtower/container"
+ "github.com/v2tec/watchtower/container"
)
func watchtowerContainersFilter(c container.Container) bool { return c.IsWatchtower() }
diff --git a/actions/update.go b/actions/update.go
index 85ca2d4..229561c 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -4,7 +4,7 @@ import (
"math/rand"
"time"
- "github.com/CenturyLinkLabs/watchtower/container"
+ "github.com/v2tec/watchtower/container"
log "github.com/Sirupsen/logrus"
)
diff --git a/container/mockclient/mock.go b/container/mockclient/mock.go
index 2338849..df2fc53 100644
--- a/container/mockclient/mock.go
+++ b/container/mockclient/mock.go
@@ -3,7 +3,7 @@ package mockclient
import (
"time"
- "github.com/CenturyLinkLabs/watchtower/container"
+ "github.com/v2tec/watchtower/container"
"github.com/stretchr/testify/mock"
)
diff --git a/container/mockclient/mock_test.go b/container/mockclient/mock_test.go
index 89984e3..234a218 100644
--- a/container/mockclient/mock_test.go
+++ b/container/mockclient/mock_test.go
@@ -4,7 +4,7 @@ import (
"reflect"
"testing"
- "github.com/CenturyLinkLabs/watchtower/container"
+ "github.com/v2tec/watchtower/container"
)
func TestMockInterface(t *testing.T) {
diff --git a/main.go b/main.go
index 678d5f6..7bf8532 100644
--- a/main.go
+++ b/main.go
@@ -1,4 +1,4 @@
-package main // import "github.com/CenturyLinkLabs/watchtower"
+package main // import "github.com/v2tec/watchtower"
import (
"fmt"
@@ -8,10 +8,10 @@ import (
"syscall"
"time"
- "github.com/CenturyLinkLabs/watchtower/actions"
- "github.com/CenturyLinkLabs/watchtower/container"
log "github.com/Sirupsen/logrus"
"github.com/urfave/cli"
+ "github.com/v2tec/watchtower/actions"
+ "github.com/v2tec/watchtower/container"
)
var (
From 3b05108481e1b8875a67b67854cc72fb1bd2557b Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 19:20:39 +0100
Subject: [PATCH 008/761] Do not publish docker images for the time being. This
will be setup differently.
---
Dockerfile | 6 ------
circle.yml | 11 -----------
2 files changed, 17 deletions(-)
delete mode 100644 Dockerfile
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 075b956..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM centurylink/ca-certs
-MAINTAINER CenturyLink Labs
-LABEL "com.centurylinklabs.watchtower"="true"
-
-COPY watchtower /
-ENTRYPOINT ["/watchtower"]
diff --git a/circle.yml b/circle.yml
index 29e0f79..d854e4c 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,8 +1,6 @@
machine:
services:
- docker
- environment:
- IMAGE_NAME: centurylink/watchtower
dependencies:
override:
@@ -11,12 +9,3 @@ dependencies:
test:
override:
- docker run -v $(pwd):/src centurylink/golang-builder:latest --test
-
-deployment:
- hub:
- branch: master
- commands:
- - docker run -v $(pwd):/src centurylink/golang-builder:latest
- - docker build -t $IMAGE_NAME:latest .
- - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- - docker push $IMAGE_NAME:latest
From 525dfea3f2ce8448a8ce0fd548b7e7963f77d204 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 30 Dec 2016 00:23:02 +0100
Subject: [PATCH 009/761] Possibility to define a cron expression which
specifies when to check for updated images. This allows to have a schedule in
which updates should be made and therefore one could define a maintenance
window.
---
README.md | 1 +
main.go | 81 ++++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 58 insertions(+), 24 deletions(-)
diff --git a/README.md b/README.md
index 0e75902..0657e26 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@ docker run --rm v2tec/watchtower --help
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
+* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both.
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
diff --git a/main.go b/main.go
index 7bf8532..3323d60 100644
--- a/main.go
+++ b/main.go
@@ -4,20 +4,21 @@ import (
"fmt"
"os"
"os/signal"
- "sync"
"syscall"
"time"
+ "strconv"
+
log "github.com/Sirupsen/logrus"
+ "github.com/robfig/cron"
"github.com/urfave/cli"
"github.com/v2tec/watchtower/actions"
"github.com/v2tec/watchtower/container"
)
var (
- wg sync.WaitGroup
client container.Client
- pollInterval time.Duration
+ scheduleSpec string
cleanup bool
noRestart bool
)
@@ -45,6 +46,11 @@ func main() {
Value: 300,
EnvVar: "WATCHTOWER_POLL_INTERVAL",
},
+ cli.StringFlag{
+ Name: "schedule, s",
+ Usage: "the cron expression which defines when to update",
+ EnvVar: "WATCHTOWER_SCHEDULE",
+ },
cli.BoolFlag{
Name: "no-pull",
Usage: "do not pull new images",
@@ -86,7 +92,17 @@ func before(c *cli.Context) error {
log.SetLevel(log.DebugLevel)
}
- pollInterval = time.Duration(c.Int("interval")) * time.Second
+ pollingSet := c.IsSet("interval")
+ cronSet := c.IsSet("schedule")
+
+ if pollingSet && cronSet {
+ log.Fatal("Only schedule or interval can be defined, not both.")
+ } else if cronSet {
+ scheduleSpec = c.String("schedule")
+ } else {
+ scheduleSpec = "@every " + strconv.Itoa(c.Int("interval")) + "s"
+ }
+
cleanup = c.GlobalBool("cleanup")
noRestart = c.GlobalBool("no-restart")
@@ -97,40 +113,57 @@ func before(c *cli.Context) error {
}
client = container.NewClient(!c.GlobalBool("no-pull"))
-
- handleSignals()
return nil
}
-func start(c *cli.Context) {
+func start(c *cli.Context) error {
names := c.Args()
if err := actions.CheckPrereqs(client, cleanup); err != nil {
log.Fatal(err)
}
- for {
- wg.Add(1)
- if err := actions.Update(client, names, cleanup, noRestart); err != nil {
- fmt.Println(err)
- }
- wg.Done()
+ tryLockSem := make(chan bool, 1)
+ tryLockSem <- true
- time.Sleep(pollInterval)
+ cron := cron.New()
+ err := cron.AddFunc(
+ scheduleSpec,
+ func() {
+ select {
+ case v := <-tryLockSem:
+ defer func() { tryLockSem <- v }()
+ if err := actions.Update(client, names, cleanup, noRestart); err != nil {
+ fmt.Println(err)
+ }
+ default:
+ log.Debug("Skipped another update already running.")
+ }
+
+ nextRuns := cron.Entries()
+ if len(nextRuns) > 0 {
+ log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
+ }
+ })
+
+ if err != nil {
+ return err
}
-}
-func handleSignals() {
+ log.Info("First run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ cron.Start()
+
// Graceful shut-down on SIGINT/SIGTERM
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt)
- signal.Notify(c, syscall.SIGTERM)
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, os.Interrupt)
+ signal.Notify(interrupt, syscall.SIGTERM)
- go func() {
- <-c
- wg.Wait()
- os.Exit(1)
- }()
+ <-interrupt
+ cron.Stop()
+ log.Info("Waiting for running update to be finished...")
+ <-tryLockSem
+ os.Exit(1)
+ return nil
}
func setEnvOptStr(env string, opt string) error {
From 63aa3efb1158a8d155eae968ff0a5b093bb8aa3d Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 20:49:25 +0100
Subject: [PATCH 010/761] Added glide for vendoring dependencies. - Docker to
1.13.x
---
.gitignore | 2 +
CONTRIBUTING.md | 22 ++----
circle.yml | 2 +
container/trust.go | 12 +--
glide.lock | 186 +++++++++++++++++++++++++++++++++++++++++++++
glide.yaml | 28 +++++++
6 files changed, 229 insertions(+), 23 deletions(-)
create mode 100644 glide.lock
create mode 100644 glide.yaml
diff --git a/.gitignore b/.gitignore
index 03e7faa..40e981a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
watchtower
+vendor
+.glide
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5d75c60..3d427fb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,21 +16,9 @@ cd watchtower
## Building and testing
watchtower is a go application and is built with go commands. The following commands assume that you are at the root level of your repo.
```bash
-go get ./... # analyzes and retrieves package dependencies
-go build # compiles and packages an executable binary, watchtower
-go test # runs tests
-./watchtower # runs the application (outside of a container)
+go get -u github.com/Masterminds/glide # installs glide for vendoring
+glide install # retrieves package dependencies
+go build # compiles and packages an executable binary, watchtower
+go test # runs tests
+./watchtower # runs the application (outside of a container)
```
-
-### Building the docker image
-watchtower is packaged and distributed as a docker image. A [golang-builder](https://github.com/CenturyLinkLabs/golang-builder) is used to package the go code and its
-dependencies as a minimally-sized application. The application binary is then layered into to a minimal docker image (see `Dockerfile`), so that the entire image is <10MB.
-See `circle.yml` for further details.The following commands assume that you are at the root level of your repo (i.e. `watchtower/`).
-
-```bash
-docker pull centurylink/golang-builder:latest # download the builder
-docker run -v $(pwd):/src centurylink/golang-builder:latest # build the minimal binary
-docker build -t /watchtower:latest . # build the docker image
-docker run -v /var/run/docker.sock:/var/run/docker.sock /watchtower # run the application (inside of a container)
-```
-
diff --git a/circle.yml b/circle.yml
index d854e4c..cd239b4 100644
--- a/circle.yml
+++ b/circle.yml
@@ -5,7 +5,9 @@ machine:
dependencies:
override:
- docker pull centurylink/golang-builder:latest
+ - go get -u github.com/Masterminds/glide
test:
override:
+ - glide install
- docker run -v $(pwd):/src centurylink/golang-builder:latest --test
diff --git a/container/trust.go b/container/trust.go
index b064032..a472649 100644
--- a/container/trust.go
+++ b/container/trust.go
@@ -4,13 +4,14 @@ import (
"errors"
"os"
"strings"
+
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/reference"
"github.com/docker/docker/cli/command"
- "github.com/docker/docker/cli/config"
- "github.com/docker/docker/cli/config/configfile"
- "github.com/docker/docker/cli/config/credentials"
+ "github.com/docker/docker/cliconfig"
+ "github.com/docker/docker/cliconfig/configfile"
+ "github.com/docker/docker/cliconfig/credentials"
)
/**
@@ -35,7 +36,7 @@ func EncodedEnvAuth(ref string) (string, error) {
username := os.Getenv("REPO_USER")
password := os.Getenv("REPO_PASS")
if username != "" && password != "" {
- auth := types.AuthConfig {
+ auth := types.AuthConfig{
Username: username,
Password: password,
}
@@ -58,7 +59,7 @@ func EncodedConfigAuth(ref string) (string, error) {
if configDir == "" {
configDir = "/"
}
- configFile, err := config.Load(configDir)
+ configFile, err := cliconfig.Load(configDir)
if err != nil {
log.Errorf("Unable to find default config file %s", err)
return "", err
@@ -80,7 +81,6 @@ func ParseServerAddress(ref string) (string, error) {
}
parts := strings.Split(repository, "/")
return parts[0], nil
-
}
// CredentialsStore returns a new credentials store based
diff --git a/glide.lock b/glide.lock
new file mode 100644
index 0000000..7ae50d9
--- /dev/null
+++ b/glide.lock
@@ -0,0 +1,186 @@
+hash: 9ddd729b207d71ce16ae9a0282ac87a046b9161ac4f15b108e86a03367172516
+updated: 2017-01-24T20:45:43.1914053+01:00
+imports:
+- name: github.com/Azure/go-ansiterm
+ version: 388960b655244e76e24c75f48631564eaefade62
+ subpackages:
+ - winterm
+- name: github.com/davecgh/go-spew
+ version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
+ subpackages:
+ - spew
+- name: github.com/docker/distribution
+ version: 28602af35aceda2f8d571bad7ca37a54cf0250bc
+ subpackages:
+ - context
+ - digest
+ - reference
+ - registry/api/errcode
+ - registry/api/v2
+ - registry/client
+ - registry/client/auth
+ - registry/client/auth/challenge
+ - registry/client/transport
+ - registry/storage/cache
+ - registry/storage/cache/memory
+ - uuid
+- name: github.com/docker/docker
+ version: 49bf474f9ed7ce7143a59d1964ff7b7fd9b52178
+ subpackages:
+ - api
+ - api/server/httputils
+ - api/types
+ - api/types/blkiodev
+ - api/types/container
+ - api/types/events
+ - api/types/filters
+ - api/types/mount
+ - api/types/network
+ - api/types/reference
+ - api/types/registry
+ - api/types/strslice
+ - api/types/swarm
+ - api/types/time
+ - api/types/versions
+ - api/types/volume
+ - cli/command
+ - cli/flags
+ - cliconfig
+ - cliconfig/configfile
+ - cliconfig/credentials
+ - client
+ - daemon/graphdriver
+ - dockerversion
+ - image
+ - image/v1
+ - layer
+ - oci
+ - opts
+ - pkg/archive
+ - pkg/chrootarchive
+ - pkg/fileutils
+ - pkg/homedir
+ - pkg/httputils
+ - pkg/idtools
+ - pkg/ioutils
+ - pkg/jsonlog
+ - pkg/jsonmessage
+ - pkg/longpath
+ - pkg/mount
+ - pkg/parsers/kernel
+ - pkg/plugingetter
+ - pkg/plugins
+ - pkg/plugins/transport
+ - pkg/pools
+ - pkg/promise
+ - pkg/random
+ - pkg/reexec
+ - pkg/stringid
+ - pkg/system
+ - pkg/tarsum
+ - pkg/term
+ - pkg/term/windows
+ - pkg/tlsconfig
+ - pkg/useragent
+ - plugin/v2
+ - reference
+ - registry
+- name: github.com/docker/docker-credential-helpers
+ version: f72c04f1d8e71959a6d103f808c50ccbad79b9fd
+ subpackages:
+ - client
+ - credentials
+- name: github.com/docker/go-connections
+ version: 4ccf312bf1d35e5dbda654e57a9be4c3f3cd0366
+ subpackages:
+ - nat
+ - sockets
+ - tlsconfig
+- name: github.com/docker/go-units
+ version: 8a7beacffa3009a9ac66bad506b18ffdd110cf97
+- name: github.com/docker/libtrust
+ version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
+- name: github.com/golang/protobuf
+ version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a
+ subpackages:
+ - proto
+- name: github.com/gorilla/context
+ version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
+- name: github.com/gorilla/mux
+ version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
+- name: github.com/inconshreveable/mousetrap
+ version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
+- name: github.com/mattn/go-shellwords
+ version: f4e566c536cf69158e808ec28ef4182a37fdc981
+- name: github.com/Microsoft/go-winio
+ version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
+- name: github.com/opencontainers/runc
+ version: 2f7393a47307a16f8cee44a37b262e8b81021e3e
+ repo: https://github.com/docker/runc.git
+ subpackages:
+ - libcontainer/configs
+ - libcontainer/devices
+ - libcontainer/system
+ - libcontainer/user
+- name: github.com/opencontainers/runtime-spec
+ version: 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
+ subpackages:
+ - specs-go
+- name: github.com/pkg/errors
+ version: 839d9e913e063e28dfd0e6c7b7512793e0a48be9
+- name: github.com/pmezard/go-difflib
+ version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
+ subpackages:
+ - difflib
+- name: github.com/robfig/cron
+ version: 9585fd555638e77bba25f25db5c44b41f264aeb7
+- name: github.com/Sirupsen/logrus
+ version: d26492970760ca5d33129d2d799e34be5c4782eb
+- name: github.com/spf13/cobra
+ version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
+ repo: https://github.com/dnephin/cobra.git
+- name: github.com/spf13/pflag
+ version: dabebe21bf790f782ea4c7bbd2efc430de182afd
+- name: github.com/stretchr/objx
+ version: cbeaeb16a013161a98496fad62933b1d21786672
+- name: github.com/stretchr/testify
+ version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
+ subpackages:
+ - assert
+ - mock
+- name: github.com/urfave/cli
+ version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
+- name: github.com/vbatts/tar-split
+ version: d3f1b54304d656376e58f9406a9cb4775799a357
+ subpackages:
+ - archive/tar
+ - tar/asm
+ - tar/storage
+- name: golang.org/x/net
+ version: 2beffdc2e92c8a3027590f898fe88f69af48a3f8
+ repo: https://github.com/tonistiigi/net.git
+ subpackages:
+ - context
+ - context/ctxhttp
+ - http2
+ - http2/hpack
+ - internal/timeseries
+ - proxy
+ - trace
+- name: golang.org/x/sys
+ version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
+ subpackages:
+ - unix
+ - windows
+- name: google.golang.org/grpc
+ version: b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21
+ subpackages:
+ - codes
+ - credentials
+ - grpclog
+ - internal
+ - metadata
+ - naming
+ - peer
+ - transport
+testImports: []
diff --git a/glide.yaml b/glide.yaml
new file mode 100644
index 0000000..18be0e1
--- /dev/null
+++ b/glide.yaml
@@ -0,0 +1,28 @@
+package: github.com/stffabi/watchtower
+import:
+- package: github.com/Sirupsen/logrus
+ version: ~0.11.x
+- package: github.com/docker/docker
+ version: ~1.13.x
+ subpackages:
+ - api/types
+ - api/types/container
+ - api/types/network
+ - api/types/reference
+ - cli/command
+ - cliconfig
+ - cliconfig/configfile
+ - cliconfig/credentials
+ - client
+- package: github.com/stretchr/testify
+ version: ~1.1.4
+ subpackages:
+ - mock
+- package: github.com/urfave/cli
+ version: ~1.19.1
+- package: golang.org/x/net
+ repo: https://github.com/tonistiigi/net.git
+ subpackages:
+ - context
+- package: github.com/robfig/cron
+ version: 9585fd555638e77bba25f25db5c44b41f264aeb7
From eda5391d0a1d50046952acfcc370d8f639b1f0c7 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 21:05:27 +0100
Subject: [PATCH 011/761] go fmt...
---
actions/update.go | 2 +-
container/client.go | 13 ++++++-------
container/mockclient/mock.go | 2 +-
container/util.go | 4 ----
container/util_test.go | 1 -
5 files changed, 8 insertions(+), 14 deletions(-)
diff --git a/actions/update.go b/actions/update.go
index 229561c..c537228 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -4,8 +4,8 @@ import (
"math/rand"
"time"
- "github.com/v2tec/watchtower/container"
log "github.com/Sirupsen/logrus"
+ "github.com/v2tec/watchtower/container"
)
var (
diff --git a/container/client.go b/container/client.go
index 62dbd36..0e5b0d0 100644
--- a/container/client.go
+++ b/container/client.go
@@ -2,12 +2,12 @@ package container
import (
"fmt"
- "time"
"io/ioutil"
+ "time"
log "github.com/Sirupsen/logrus"
- dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/api/types"
+ dockerclient "github.com/docker/docker/client"
"golang.org/x/net/context"
)
@@ -76,7 +76,7 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
}
c := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
- if (fn(c)) {
+ if fn(c) {
cs = append(cs, c)
}
}
@@ -115,7 +115,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
}
func (client dockerClient) StartContainer(c Container) error {
- bg := context.Background();
+ bg := context.Background()
config := c.runtimeConfig()
hostConfig := c.hostConfig()
name := c.Name()
@@ -144,7 +144,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
if client.pullImages {
log.Debugf("Pulling %s for %s", imageName, c.Name())
-
+
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
auth, err := EncodedAuth(imageName)
if err != nil {
@@ -163,7 +163,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return false, err
}
defer response.Close()
-
+
// the pull request will be aborted prematurely unless the response is read
_, err = ioutil.ReadAll(response)
}
@@ -179,7 +179,6 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
} else {
log.Debugf("No new images found for %s", c.Name())
}
-
return false, nil
}
diff --git a/container/mockclient/mock.go b/container/mockclient/mock.go
index df2fc53..62cd321 100644
--- a/container/mockclient/mock.go
+++ b/container/mockclient/mock.go
@@ -3,8 +3,8 @@ package mockclient
import (
"time"
- "github.com/v2tec/watchtower/container"
"github.com/stretchr/testify/mock"
+ "github.com/v2tec/watchtower/container"
)
type MockClient struct {
diff --git a/container/util.go b/container/util.go
index 8947c82..3db01d1 100644
--- a/container/util.go
+++ b/container/util.go
@@ -1,8 +1,5 @@
package container
-import (
-)
-
func sliceEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
@@ -65,4 +62,3 @@ func structMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
return m
}
-
diff --git a/container/util_test.go b/container/util_test.go
index 9ed551d..c4d5ca7 100644
--- a/container/util_test.go
+++ b/container/util_test.go
@@ -63,4 +63,3 @@ func TestStructMapSubtract(t *testing.T) {
assert.Equal(t, map[string]struct{}{"a": x, "b": x, "c": x}, m1)
assert.Equal(t, map[string]struct{}{"a": x, "c": x}, m2)
}
-
From b7efbc3059afd633c7be60d83df6ec322eb30c9f Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 21:41:32 +0100
Subject: [PATCH 012/761] Set minimum required API Version of docker to 1.24,
this basically means we require at least docker 1.12.x or newer, therefore we
also support docker 1.13.x.
---
README.md | 1 -
main.go | 11 +++++------
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 0657e26..b92958e 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,6 @@ docker run --rm v2tec/watchtower --help
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
-* `--apiversion` Specify the minimum docker api version. watchtower will only communicate with docker servers running this api version or later.
* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
* `--help` Show documentation about the supported flags.
diff --git a/main.go b/main.go
index 3323d60..0ae3416 100644
--- a/main.go
+++ b/main.go
@@ -16,6 +16,10 @@ import (
"github.com/v2tec/watchtower/container"
)
+// DockerAPIMinVersion is the version of the docker API, which is minimally required by
+// watchtower. Currently we require at least API 1.24 and therefore Docker 1.12 or later.
+const DockerAPIMinVersion string = "1.24"
+
var (
client container.Client
scheduleSpec string
@@ -75,11 +79,6 @@ func main() {
Name: "debug",
Usage: "enable debug mode with verbose logging",
},
- cli.StringFlag{
- Name: "apiversion",
- Usage: "the version of the docker api",
- EnvVar: "DOCKER_API_VERSION",
- },
}
if err := app.Run(os.Args); err != nil {
@@ -190,7 +189,7 @@ func envConfig(c *cli.Context) error {
err = setEnvOptStr("DOCKER_HOST", c.GlobalString("host"))
err = setEnvOptBool("DOCKER_TLS_VERIFY", c.GlobalBool("tlsverify"))
- err = setEnvOptStr("DOCKER_API_VERSION", c.GlobalString("apiversion"))
+ err = setEnvOptStr("DOCKER_API_VERSION", DockerAPIMinVersion)
return err
}
From 208678872ecebd58da41aaa739a357659daf9f4b Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 24 Jan 2017 21:56:27 +0100
Subject: [PATCH 013/761] Fixed package path.
---
glide.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/glide.yaml b/glide.yaml
index 18be0e1..8478cfa 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,4 +1,4 @@
-package: github.com/stffabi/watchtower
+package: github.com/v2tec/watchtower
import:
- package: github.com/Sirupsen/logrus
version: ~0.11.x
From a5c3e1e05aa7d91d7815fceb751177b68929613b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tanguy=20=E2=A7=93=20Herrmann?=
Date: Tue, 31 Jan 2017 11:00:22 +0100
Subject: [PATCH 014/761] Make the algorithm follow docker-compose more
precisely
https://github.com/v2tec/watchtower/pull/40#discussion_r97719974
---
container/client.go | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/container/client.go b/container/client.go
index d977f99..9824b81 100644
--- a/container/client.go
+++ b/container/client.go
@@ -134,19 +134,12 @@ func (client dockerClient) StartContainer(c Container) error {
name := c.Name()
- log.Infof("Starting %s", name)
+ log.Infof("Creating %s", name)
creation, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, name)
if err != nil {
return err
}
- log.Debugf("Starting container %s (%s)", name, creation.ID)
-
- err = client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{})
- if err != nil {
- return err
- }
-
for k := range simpleNetworkConfig.EndpointsConfig {
err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
if err != nil {
@@ -160,6 +153,14 @@ func (client dockerClient) StartContainer(c Container) error {
return err
}
}
+
+ 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
}
From 4da940c19e67f937571f85f656d3e410db8d6feb Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 1 Feb 2017 19:03:54 +0100
Subject: [PATCH 015/761] Copy watchtower binary for ci builds to artifacts.
---
circle.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/circle.yml b/circle.yml
index cd239b4..56b70e9 100644
--- a/circle.yml
+++ b/circle.yml
@@ -11,3 +11,9 @@ test:
override:
- glide install
- docker run -v $(pwd):/src centurylink/golang-builder:latest --test
+
+deployment:
+ ci:
+ branch: /.*/
+ commands:
+ - cp -r ./watchtower $CIRCLE_ARTIFACTS
\ No newline at end of file
From 9f099cf3529a3e5023646710d7f433726a9dc1a3 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Thu, 2 Feb 2017 18:37:12 +0100
Subject: [PATCH 016/761] - Use GoBuilder container for building and release
tags with goreleaser. - Add version to cli.
---
.gitignore | 3 ++-
circle.yml | 21 ++++++++++++++-----
goreleaser.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++
main.go | 3 +++
4 files changed, 76 insertions(+), 6 deletions(-)
create mode 100644 goreleaser.yml
diff --git a/.gitignore b/.gitignore
index 40e981a..e43f117 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
watchtower
vendor
-.glide
\ No newline at end of file
+.glide
+dist
\ No newline at end of file
diff --git a/circle.yml b/circle.yml
index 56b70e9..0ccf4b1 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,19 +1,30 @@
machine:
services:
- docker
+ environment:
+ IS_RELEASE: $(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
+ BUILD_IMAGE: v2tec/gobuilder:0.1.0_go1.7.4-glide0.12.3-goreleaser0.6.2
dependencies:
override:
- - docker pull centurylink/golang-builder:latest
- - go get -u github.com/Masterminds/glide
+ - git fetch --tags
+ - docker pull $BUILD_IMAGE
+
+compile:
+ override:
+ - docker run -v "$PWD":/src -e GITHUB_TOKEN=$GITHUB_TOKEN $BUILD_IMAGE $IS_RELEASE $CIRCLE_BRANCH-$CIRCLE_SHA1
test:
override:
- - glide install
- - docker run -v $(pwd):/src centurylink/golang-builder:latest --test
+ - echo "Tests included in compile step."
deployment:
ci:
branch: /.*/
commands:
- - cp -r ./watchtower $CIRCLE_ARTIFACTS
\ No newline at end of file
+ - cp -r ./dist/* $CIRCLE_ARTIFACTS
+ release:
+ tag: /v[0-9]+(\.[0-9]+)*/
+ owner: v2tec
+ commands:
+ - cp -r ./dist/* $CIRCLE_ARTIFACTS
diff --git a/goreleaser.yml b/goreleaser.yml
new file mode 100644
index 0000000..217774b
--- /dev/null
+++ b/goreleaser.yml
@@ -0,0 +1,55 @@
+# Build customization
+build:
+ # Path to main.go file.
+ # Default is `main.go`
+ main: ./main.go
+
+ # Custom ldflags.
+ # Default is `-s -w`
+ ldflags: -s -w
+
+ # GOOS list to build in.
+ # For more info refer to https://golang.org/doc/install/source#environment
+ # Defaults are darwin and linux
+ goos:
+ - linux
+
+ # GOARCH to build in.
+ # For more info refer to https://golang.org/doc/install/source#environment
+ # Defaults are 386 and amd64
+ goarch:
+ - amd64
+
+# Archive customization
+archive:
+ # You can change the name of the archive.
+ # This is parsed with Golang template engine and the following variables
+ # are available:
+ # - BinaryName
+ # - Version
+ # - Os
+ # - Arch
+ # The default is `{{.BinaryName}}_{{.Os}}_{{.Arch}}`
+ name_template: "{{.BinaryName}}_{{.Os}}_{{.Arch}}"
+
+ # Archive format. Valid options are `tar.gz` and `zip`.
+ # Default is `zip`
+ format: tar.gz
+
+ # Replacements for GOOS and GOARCH on the archive name.
+ # The keys should be valid GOOS or GOARCH values followed by your custom
+ # replacements.
+ # By default, `replacements` replace GOOS and GOARCH values with valid outputs
+ # of `uname -s` and `uname -m` respectively.
+ replacements:
+ arm: arm
+ amd64: amd64
+ 386: 386
+ darwin: macOS
+ linux: linux
+
+ # Additional files you want to add to the archive.
+ # Defaults are any files matching `LICENCE*`, `LICENSE*`,
+ # `README*` and `CHANGELOG*` (case-insensitive)
+ files:
+ - LICENSE.md
diff --git a/main.go b/main.go
index 0ae3416..e97c4d8 100644
--- a/main.go
+++ b/main.go
@@ -20,6 +20,8 @@ import (
// watchtower. Currently we require at least API 1.24 and therefore Docker 1.12 or later.
const DockerAPIMinVersion string = "1.24"
+var version = "master"
+
var (
client container.Client
scheduleSpec string
@@ -34,6 +36,7 @@ func init() {
func main() {
app := cli.NewApp()
app.Name = "watchtower"
+ app.Version = version
app.Usage = "Automatically update running Docker containers"
app.Before = before
app.Action = start
From 265ae80099695baa75425b5a4cfd32e5d9c88845 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 3 Feb 2017 07:39:13 +0100
Subject: [PATCH 017/761] Added arm release builds.
---
goreleaser.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/goreleaser.yml b/goreleaser.yml
index 217774b..933440f 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -19,6 +19,7 @@ build:
# Defaults are 386 and amd64
goarch:
- amd64
+ - arm
# Archive customization
archive:
From 8e6c9fd529292375310c90d3aea89d5e1acf54f9 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Thu, 16 Feb 2017 16:41:50 +0100
Subject: [PATCH 018/761] Name release tarballs for arm architecture armhf.
---
goreleaser.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/goreleaser.yml b/goreleaser.yml
index 933440f..8b89760 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -43,7 +43,7 @@ archive:
# By default, `replacements` replace GOOS and GOARCH values with valid outputs
# of `uname -s` and `uname -m` respectively.
replacements:
- arm: arm
+ arm: armhf
amd64: amd64
386: 386
darwin: macOS
From e43cf1ffda3912e03d8f4e90601ec4fff0a5e060 Mon Sep 17 00:00:00 2001
From: Bill Butler
Date: Thu, 16 Mar 2017 09:10:08 -0400
Subject: [PATCH 019/761] Update README.md
Provide more clarity for private repos and add a docker-compose example.
---
README.md | 38 ++++++++++++++++++++++++++++----------
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index b92958e..8bac1e6 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ With watchtower you can update the running version of your containerized app sim
For example, let's say you were running watchtower along with an instance of *centurylink/wetty-cli* image:
-```
+```bash
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
@@ -30,7 +30,7 @@ Since the watchtower code needs to interact with the Docker API in order to moni
Run the `watchtower` container with the following command:
-```
+```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
@@ -40,7 +40,7 @@ docker run -d \
If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
-```
+```bash
docker run -d \
--name watchtower \
-v /home//.docker/config.json:/config.json \
@@ -48,11 +48,29 @@ docker run -d \
drud/watchtower container_to_watch --debug
```
+If you mount the config file as described below, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
+
+```json
+version: "3"
+services:
+ cavo:
+ image: index.docker.io//:
+ ports:
+ - "443:3443"
+ - "80:3080"
+ watchtower:
+ image: v2tec/watchtower
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /root/.docker/config.json:/config.json
+ command: --interval 30
+```
+
### Arguments
By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when launching watchtower.
-```
+```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
@@ -67,7 +85,7 @@ When no arguments are specified, watchtower will monitor all running containers.
Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string:
-```
+```bash
docker run --rm v2tec/watchtower --help
```
@@ -93,13 +111,13 @@ If your container should be shutdown with a different signal you can communicate
This label can be coded directly into your image by using the `LABEL` instruction in your Dockerfile:
-```
+```docker
LABEL com.centurylinklabs.watchtower.stop-signal="SIGHUP"
```
Or, it can be specified as part of the `docker run` command line:
-```
+```bash
docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimage
```
@@ -107,7 +125,7 @@ docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimag
By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container itself). However, it is possible to configure watchtower to monitor a remote Docker endpoint. When starting the watchtower container you can specify a remote Docker endpoint with either the `--host` flag or the `DOCKER_HOST` environment variable:
-```
+```bash
docker run -d \
--name watchtower \
v2tec/watchtower --host "tcp://10.0.1.2:2375"
@@ -115,7 +133,7 @@ docker run -d \
or
-```
+```bash
docker run -d \
--name watchtower \
-e DOCKER_HOST="tcp://10.0.1.2:2375" \
@@ -132,7 +150,7 @@ The *docker-machine* certificates for a particular host can be located by execut
With the certificates mounted into the watchtower container you need to specify the `--tlsverify` flag to enable verification of the certificate:
-```
+```bash
docker run -d \
--name watchtower \
-e DOCKER_HOST=$DOCKER_HOST \
From f8a05cb8abdf771e8dcca6d3ac9e058080404b46 Mon Sep 17 00:00:00 2001
From: Robotex
Date: Fri, 24 Mar 2017 22:58:00 +0100
Subject: [PATCH 020/761] Added host network check
---
container/client.go | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/container/client.go b/container/client.go
index 9824b81..5055e7a 100644
--- a/container/client.go
+++ b/container/client.go
@@ -140,18 +140,22 @@ func (client dockerClient) StartContainer(c Container) error {
return err
}
- for k := range simpleNetworkConfig.EndpointsConfig {
- err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
- if err != nil {
- return err
- }
- }
+ if !(hostConfig.NetworkMode.IsHost()) {
- for k, v := range networkConfig.EndpointsConfig {
- err = client.api.NetworkConnect(bg, k, creation.ID, v)
- if err != nil {
- return err
+ for k := range simpleNetworkConfig.EndpointsConfig {
+ err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
+ if err != nil {
+ return err
+ }
}
+
+ for k, v := range networkConfig.EndpointsConfig {
+ err = client.api.NetworkConnect(bg, k, creation.ID, v)
+ if err != nil {
+ return err
+ }
+ }
+
}
log.Debugf("Starting container %s (%s)", name, creation.ID)
From 52e73d7a8a9e5a899fec498bdfb3f7833821caf9 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 12 Apr 2017 08:40:01 +0200
Subject: [PATCH 021/761] Do not initiate a RemoveContainer for containers
which have AutoRemove (--rm) active.
fixes GH-71
---
container/client.go | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/container/client.go b/container/client.go
index 5055e7a..fb94071 100644
--- a/container/client.go
+++ b/container/client.go
@@ -101,10 +101,14 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) 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 c.containerInfo.HostConfig.AutoRemove {
+ log.Debugf("AutoRemove container %s, skipping ContainerRemove call.", c.ID())
+ } else {
+ log.Debugf("Removing container %s", c.ID())
- if err := client.api.ContainerRemove(bg, c.ID(), types.ContainerRemoveOptions{Force: true, RemoveVolumes: false}); err != nil {
- return err
+ if err := client.api.ContainerRemove(bg, c.ID(), types.ContainerRemoveOptions{Force: true, RemoveVolumes: false}); err != nil {
+ return err
+ }
}
// Wait for container to be removed. In this case an error is a good thing
From bee4607d9a94f689b8ff0945765be9851df14d9a Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 12 Apr 2017 18:32:00 +0200
Subject: [PATCH 022/761] Output "Checking containers for updated images" as
debug entry. fixes GH-66
---
actions/update.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actions/update.go b/actions/update.go
index c537228..b41d34c 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -35,7 +35,7 @@ func containerFilter(names []string) container.Filter {
// any of the images, the associated containers are stopped and restarted with
// the new image.
func Update(client container.Client, names []string, cleanup bool, noRestart bool) error {
- log.Info("Checking containers for updated images")
+ log.Debug("Checking containers for updated images")
containers, err := client.ListContainers(containerFilter(names))
if err != nil {
From 1eb28c99fd9e281a559f6a4b9a96ca66adb2ad4b Mon Sep 17 00:00:00 2001
From: Mark Woodbridge
Date: Fri, 19 May 2017 11:48:10 +0100
Subject: [PATCH 023/761] Correct repository owner
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 8bac1e6..ef2ce81 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ docker run -d \
--name watchtower \
-v /home//.docker/config.json:/config.json \
-v /var/run/docker.sock:/var/run/docker.sock \
- drud/watchtower container_to_watch --debug
+ v2tec/watchtower container_to_watch --debug
```
If you mount the config file as described below, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
From 5adb143f628fbfe2e16fd29349d4eb541c01fa0e Mon Sep 17 00:00:00 2001
From: Rodrigo Damazio Bovendorp
Date: Sun, 29 Oct 2017 23:30:44 -0700
Subject: [PATCH 024/761] Adding basic (but flexible) notification system which
hooks into logrus. This only adds e-mail notifications, but others could be
easily done. In many cases, adding another existing logrus hook will be
sufficient.
---
main.go | 40 ++++++++++++-
notifications/email.go | 115 ++++++++++++++++++++++++++++++++++++++
notifications/notifier.go | 46 +++++++++++++++
3 files changed, 199 insertions(+), 2 deletions(-)
create mode 100644 notifications/email.go
create mode 100644 notifications/notifier.go
diff --git a/main.go b/main.go
index e97c4d8..0cf3465 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,6 @@
package main // import "github.com/v2tec/watchtower"
import (
- "fmt"
"os"
"os/signal"
"syscall"
@@ -14,6 +13,7 @@ import (
"github.com/urfave/cli"
"github.com/v2tec/watchtower/actions"
"github.com/v2tec/watchtower/container"
+ "github.com/v2tec/watchtower/notifications"
)
// DockerAPIMinVersion is the version of the docker API, which is minimally required by
@@ -27,6 +27,7 @@ var (
scheduleSpec string
cleanup bool
noRestart bool
+ notifier *notifications.Notifier
)
func init() {
@@ -82,6 +83,37 @@ func main() {
Name: "debug",
Usage: "enable debug mode with verbose logging",
},
+ cli.StringSliceFlag{
+ Name: "notifications",
+ Value: &cli.StringSlice{},
+ Usage: "notification types to send (valid: email)",
+ EnvVar: "WATCHTOWER_NOTIFICATIONS",
+ },
+ cli.StringFlag{
+ Name: "notification-email-from",
+ Usage: "Address to send notification e-mails from",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_FROM",
+ },
+ cli.StringFlag{
+ Name: "notification-email-to",
+ Usage: "Address to send notification e-mails to",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_TO",
+ },
+ cli.StringFlag{
+ Name: "notification-email-server",
+ Usage: "SMTP server to send notification e-mails through",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER",
+ },
+ cli.StringFlag{
+ Name: "notification-email-server-user",
+ Usage: "SMTP server user for sending notifications",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER",
+ },
+ cli.StringFlag{
+ Name: "notification-email-server-password",
+ Usage: "SMTP server password for sending notifications",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
+ },
}
if err := app.Run(os.Args); err != nil {
@@ -115,6 +147,8 @@ func before(c *cli.Context) error {
}
client = container.NewClient(!c.GlobalBool("no-pull"))
+ notifier = notifications.NewNotifier(c)
+
return nil
}
@@ -135,9 +169,11 @@ func start(c *cli.Context) error {
select {
case v := <-tryLockSem:
defer func() { tryLockSem <- v }()
+ notifier.StartNotification()
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
- fmt.Println(err)
+ log.Println(err)
}
+ notifier.SendNotification()
default:
log.Debug("Skipped another update already running.")
}
diff --git a/notifications/email.go b/notifications/email.go
new file mode 100644
index 0000000..9fd8774
--- /dev/null
+++ b/notifications/email.go
@@ -0,0 +1,115 @@
+package notifications
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/smtp"
+ "os"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+const (
+ emailType = "email"
+)
+
+// Implements typeNotifier, logrus.Hook
+// The default logrus email integration would have several issues:
+// - It would send one email per log output
+// - It would only send errors
+// We work around that by holding on to log entries until the update cycle is done.
+type emailTypeNotifier struct {
+ From, To string
+ Server, User, Password string
+ entries []*log.Entry
+}
+
+func newEmailNotifier(c *cli.Context) typeNotifier {
+ n := &emailTypeNotifier{
+ From: c.GlobalString("notification-email-from"),
+ To: c.GlobalString("notification-email-to"),
+ Server: c.GlobalString("notification-email-server"),
+ User: c.GlobalString("notification-email-server-user"),
+ Password: c.GlobalString("notification-email-server-password"),
+ }
+
+ log.AddHook(n)
+
+ return n
+}
+
+func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
+ emailSubject := "Watchtower updates"
+ if hostname, err := os.Hostname(); err == nil {
+ emailSubject += " on " + hostname
+ }
+ body := ""
+ for _, entry := range entries {
+ body += entry.Time.Format("2006-01-02 15:04:05") + " (" + entry.Level.String() + "): " + entry.Message + "\r\n"
+ // We don't use fields in watchtower, so don't bother sending them.
+ }
+
+ header := make(map[string]string)
+ header["From"] = e.From
+ header["To"] = e.To
+ header["Subject"] = emailSubject
+ header["MIME-Version"] = "1.0"
+ header["Content-Type"] = "text/plain; charset=\"utf-8\""
+ header["Content-Transfer-Encoding"] = "base64"
+
+ message := ""
+ for k, v := range header {
+ message += fmt.Sprintf("%s: %s\r\n", k, v)
+ }
+ message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
+
+ return []byte(message)
+}
+
+func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
+ // Do the sending in a separate goroutine so we don't block the main process.
+ msg := e.buildMessage(entries)
+ go func() {
+ auth := smtp.PlainAuth("", e.User, e.Password, e.Server)
+ err := smtp.SendMail(e.Server+":25", auth, e.From, []string{e.To}, msg)
+ if err != nil {
+ // Use fmt so it doesn't trigger another email.
+ fmt.Println("Failed to send notification email: ", err)
+ }
+ }()
+}
+
+func (e *emailTypeNotifier) StartNotification() {
+ if e.entries == nil {
+ e.entries = make([]*log.Entry, 0, 10)
+ }
+}
+
+func (e *emailTypeNotifier) SendNotification() {
+ if e.entries != nil {
+ e.sendEntries(e.entries)
+ e.entries = nil
+ }
+}
+
+func (e *emailTypeNotifier) Levels() []log.Level {
+ // TODO: Make this configurable.
+ return []log.Level{
+ log.PanicLevel,
+ log.FatalLevel,
+ log.ErrorLevel,
+ log.WarnLevel,
+ log.InfoLevel,
+ }
+}
+
+func (e *emailTypeNotifier) Fire(entry *log.Entry) error {
+ if e.entries != nil {
+ e.entries = append(e.entries, entry)
+ } else {
+ // Log output generated outside a cycle is sent immediately.
+ e.sendEntries([]*log.Entry{entry})
+ }
+ return nil
+}
diff --git a/notifications/notifier.go b/notifications/notifier.go
new file mode 100644
index 0000000..b7ec7a1
--- /dev/null
+++ b/notifications/notifier.go
@@ -0,0 +1,46 @@
+package notifications
+
+import (
+ log "github.com/Sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+type typeNotifier interface {
+ StartNotification()
+ SendNotification()
+}
+
+type Notifier struct {
+ types []typeNotifier
+}
+
+func NewNotifier(c *cli.Context) *Notifier {
+ n := &Notifier{}
+
+ // Parse types and create notifiers.
+ types := c.GlobalStringSlice("notifications")
+ for _, t := range types {
+ var tn typeNotifier
+ switch t {
+ case emailType:
+ tn = newEmailNotifier(c)
+ default:
+ log.Fatalf("Unknown notification type %q", t)
+ }
+ n.types = append(n.types, tn)
+ }
+
+ return n
+}
+
+func (n *Notifier) StartNotification() {
+ for _, t := range n.types {
+ t.StartNotification()
+ }
+}
+
+func (n *Notifier) SendNotification() {
+ for _, t := range n.types {
+ t.SendNotification()
+ }
+}
From d8586e79cad39a3c61a39dc2ccb309a1a09f26eb Mon Sep 17 00:00:00 2001
From: Rodrigo Damazio Bovendorp
Date: Sun, 29 Oct 2017 23:45:01 -0700
Subject: [PATCH 025/761] Fixing function documentation
---
notifications/notifier.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/notifications/notifier.go b/notifications/notifier.go
index b7ec7a1..a73a63e 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -10,10 +10,12 @@ type typeNotifier interface {
SendNotification()
}
+// Notifier can send log output as notification to admins, with optional batching.
type Notifier struct {
types []typeNotifier
}
+// NewNotifier creates and returns a new Notifier, using global configuration.
func NewNotifier(c *cli.Context) *Notifier {
n := &Notifier{}
@@ -33,12 +35,14 @@ func NewNotifier(c *cli.Context) *Notifier {
return n
}
+// StartNotification starts a log batch. Notifications will be accumulated after this point and only sent when SendNotification() is called.
func (n *Notifier) StartNotification() {
for _, t := range n.types {
t.StartNotification()
}
}
+// SendNotification sends any notifications accumulated since StartNotification() was called.
func (n *Notifier) SendNotification() {
for _, t := range n.types {
t.SendNotification()
From 220c09dc7ff7fac9bc2795b3e396a9ba98170f84 Mon Sep 17 00:00:00 2001
From: Rodrigo Damazio Bovendorp
Date: Sat, 25 Nov 2017 21:55:56 -0800
Subject: [PATCH 026/761] Updated README.md to document notifications
---
README.md | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/README.md b/README.md
index ef2ce81..0d263b4 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,8 @@ docker run --rm v2tec/watchtower --help
* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
* `--help` Show documentation about the supported flags.
+See below for options used to configure notifications.
+
## Linked Containers
Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
@@ -161,3 +163,34 @@ docker run -d \
## Updating Watchtower
If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *v2tec/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
+
+## Notifications
+
+Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
+The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
+
+* `email` to send notifications via e-mail
+
+To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
+
+* `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
+* `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
+* `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
+* `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
+* `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=email \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_FROM=fromaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_TO=toaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
+ v2tec/watchtower
+```
+
From de2ac9341d582546820b54fa6326b0de90c14533 Mon Sep 17 00:00:00 2001
From: Kaleb Elwert
Date: Tue, 17 Oct 2017 06:53:30 +0200
Subject: [PATCH 027/761] Add a method of enabling or disabling containers
using labels
Switch command line flag from no-enable to label-enable and simplify logic
Add basic documentation for the --label-enable flag
---
README.md | 17 +++++++++++++++++
container/client.go | 19 +++++++++++++++----
container/container.go | 18 ++++++++++++++++++
main.go | 7 ++++++-
4 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 0d263b4..9edffa1 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ docker run --rm v2tec/watchtower --help
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both.
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
+* `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
@@ -123,6 +124,22 @@ Or, it can be specified as part of the `docker run` command line:
docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimage
```
+## Selectively Watching Containers
+
+By default, watchtower will watch all containers.
+However, sometimes only some containers should be updated.
+If you need to selectively watch containers, pass the --label-enable flag on startup and set the *com.centurylinklabs.watchtower.enable* label with a value of true for the containers you want to watch.
+
+```docker
+LABEL com.centurylinklabs.watchtower.enable="true"
+```
+
+Or, it can be specified as part of the `docker run` command line:
+
+```bash
+docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
+```
+
## Remote Hosts
By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container itself). However, it is possible to configure watchtower to monitor a remote Docker endpoint. When starting the watchtower container you can specify a remote Docker endpoint with either the `--host` flag or the `DOCKER_HOST` environment variable:
diff --git a/container/client.go b/container/client.go
index fb94071..afa9a33 100644
--- a/container/client.go
+++ b/container/client.go
@@ -37,19 +37,20 @@ 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, enableLabel bool) Client {
cli, err := dockerclient.NewEnvClient()
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
}
- return dockerClient{api: cli, pullImages: pullImages}
+ return dockerClient{api: cli, pullImages: pullImages, enableLabel: enableLabel}
}
type dockerClient struct {
- api *dockerclient.Client
- pullImages bool
+ api *dockerclient.Client
+ pullImages bool
+ enableLabel bool
}
func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
@@ -77,6 +78,16 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
}
c := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
+
+ if client.enableLabel {
+ // If label filtering is enabled, containers should only be enabled
+ // if the label is specifically set to true.
+ enabledLabel, ok := c.Enabled()
+ if !ok || !enabledLabel {
+ continue
+ }
+ }
+
if fn(c) {
cs = append(cs, c)
}
diff --git a/container/container.go b/container/container.go
index c49da33..e620d92 100644
--- a/container/container.go
+++ b/container/container.go
@@ -2,6 +2,7 @@ package container
import (
"fmt"
+ "strconv"
"strings"
"github.com/docker/docker/api/types"
@@ -11,6 +12,7 @@ import (
const (
watchtowerLabel = "com.centurylinklabs.watchtower"
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
+ enableLabel = "com.centurylinklabs.watchtower.enable"
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
)
@@ -64,6 +66,22 @@ func (c Container) ImageName() string {
return imageName
}
+// Enabled returns the value of the container enabled label and if the label
+// was set.
+func (c Container) Enabled() (bool, bool) {
+ rawBool, ok := c.containerInfo.Config.Labels[enableLabel]
+ if !ok {
+ return false, false
+ }
+
+ parsedBool, err := strconv.ParseBool(rawBool)
+ if err != nil {
+ return false, false
+ }
+
+ return parsedBool, true
+}
+
// Links returns a list containing the names of all the containers to which
// this container is linked.
func (c Container) Links() []string {
diff --git a/main.go b/main.go
index 0cf3465..b359fba 100644
--- a/main.go
+++ b/main.go
@@ -79,6 +79,11 @@ func main() {
Usage: "use TLS and verify the remote",
EnvVar: "DOCKER_TLS_VERIFY",
},
+ cli.BoolFlag{
+ Name: "label-enable",
+ Usage: "watch containers where the com.centurylinklabs.watchtower.enable label is true",
+ EnvVar: "WATCHTOWER_LABEL_ENABLE",
+ },
cli.BoolFlag{
Name: "debug",
Usage: "enable debug mode with verbose logging",
@@ -146,7 +151,7 @@ func before(c *cli.Context) error {
return err
}
- client = container.NewClient(!c.GlobalBool("no-pull"))
+ client = container.NewClient(!c.GlobalBool("no-pull"), c.GlobalBool("label-enable"))
notifier = notifications.NewNotifier(c)
return nil
From d7481511c4ef1fa52dac5bacda837888a9da277d Mon Sep 17 00:00:00 2001
From: Ryan Kuba
Date: Tue, 5 Dec 2017 20:01:25 -0800
Subject: [PATCH 028/761] github/v2tec/watchtower#114 adding if statement to
accept the oneshot flag to run once and exit
---
README.md | 11 +++++++
main.go | 94 +++++++++++++++++++++++++++++++------------------------
2 files changed, 64 insertions(+), 41 deletions(-)
diff --git a/README.md b/README.md
index 9edffa1..f2b3b66 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,16 @@ docker run -d \
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other running containers will be ignored.
+If you do not want watchtower to run as a daemon you can pass a oneshot flag and remove the watchtower container after it's execution.
+
+```bash
+docker run --rm \
+-v /var/run/docker.sock:/var/run/docker.sock \
+v2tec/watchtower --oneshot nginx redis
+```
+
+In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
+
When no arguments are specified, watchtower will monitor all running containers.
### Options
@@ -90,6 +100,7 @@ docker run --rm v2tec/watchtower --help
```
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
+* `--oneshot` Run an update attempt against a container name list one time immediately and exit.
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both.
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
diff --git a/main.go b/main.go
index b359fba..09dbc98 100644
--- a/main.go
+++ b/main.go
@@ -119,6 +119,10 @@ func main() {
Usage: "SMTP server password for sending notifications",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
},
+ cli.BoolFlag{
+ Name: "oneshot",
+ Usage: "Run once now and exit",
+ },
}
if err := app.Run(os.Args); err != nil {
@@ -159,53 +163,61 @@ func before(c *cli.Context) error {
func start(c *cli.Context) error {
names := c.Args()
+ if c.GlobalBool("oneshot") {
+ log.SetLevel(log.DebugLevel)
+ log.Info("OneShot run: " + time.Now().String())
+ notifier.StartNotification()
+ if err := actions.Update(client, names, cleanup, noRestart); err != nil {
+ log.Println(err)
+ }
+ notifier.SendNotification()
+ } else {
+ if err := actions.CheckPrereqs(client, cleanup); err != nil {
+ log.Fatal(err)
+ }
- if err := actions.CheckPrereqs(client, cleanup); err != nil {
- log.Fatal(err)
- }
+ tryLockSem := make(chan bool, 1)
+ tryLockSem <- true
- tryLockSem := make(chan bool, 1)
- tryLockSem <- true
-
- cron := cron.New()
- err := cron.AddFunc(
- scheduleSpec,
- func() {
- select {
- case v := <-tryLockSem:
- defer func() { tryLockSem <- v }()
- notifier.StartNotification()
- if err := actions.Update(client, names, cleanup, noRestart); err != nil {
- log.Println(err)
+ cron := cron.New()
+ err := cron.AddFunc(
+ scheduleSpec,
+ func() {
+ select {
+ case v := <-tryLockSem:
+ defer func() { tryLockSem <- v }()
+ notifier.StartNotification()
+ if err := actions.Update(client, names, cleanup, noRestart); err != nil {
+ log.Println(err)
+ }
+ notifier.SendNotification()
+ default:
+ log.Debug("Skipped another update already running.")
}
- notifier.SendNotification()
- default:
- log.Debug("Skipped another update already running.")
- }
- nextRuns := cron.Entries()
- if len(nextRuns) > 0 {
- log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
- }
- })
+ nextRuns := cron.Entries()
+ if len(nextRuns) > 0 {
+ log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
+ }
+ })
- if err != nil {
- return err
+ if err != nil {
+ return err
+ }
+
+ log.Info("First run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ cron.Start()
+ // Graceful shut-down on SIGINT/SIGTERM
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, os.Interrupt)
+ signal.Notify(interrupt, syscall.SIGTERM)
+
+ <-interrupt
+ cron.Stop()
+ log.Info("Waiting for running update to be finished...")
+ <-tryLockSem
+ os.Exit(1)
}
-
- log.Info("First run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
- cron.Start()
-
- // Graceful shut-down on SIGINT/SIGTERM
- interrupt := make(chan os.Signal, 1)
- signal.Notify(interrupt, os.Interrupt)
- signal.Notify(interrupt, syscall.SIGTERM)
-
- <-interrupt
- cron.Stop()
- log.Info("Waiting for running update to be finished...")
- <-tryLockSem
- os.Exit(1)
return nil
}
From a3f3d471582916a7e7cfd1be9cef2b9f86e5b877 Mon Sep 17 00:00:00 2001
From: Paulo Henrique
Date: Thu, 7 Dec 2017 17:36:26 -0300
Subject: [PATCH 029/761] Fix code style.
---
main.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/main.go b/main.go
index b359fba..bf9bd21 100644
--- a/main.go
+++ b/main.go
@@ -172,7 +172,7 @@ func start(c *cli.Context) error {
scheduleSpec,
func() {
select {
- case v := <-tryLockSem:
+ case v := <- tryLockSem:
defer func() { tryLockSem <- v }()
notifier.StartNotification()
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
From 296f98fe426b302c058fc352d60be5cb9c2f23e5 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 27 Dec 2017 07:35:25 +0100
Subject: [PATCH 030/761] - Update to circleci 2.0 - Use latest gobuilder with
goreleaser which also publishes docker files.
---
circle.yml | 53 +++++++++++++++++--------------------
dockerfile/amd64/Dockerfile | 4 +++
dockerfile/armhf/Dockerfile | 4 +++
goreleaser.yml | 28 ++++++++++++++------
4 files changed, 52 insertions(+), 37 deletions(-)
create mode 100644 dockerfile/amd64/Dockerfile
create mode 100644 dockerfile/armhf/Dockerfile
diff --git a/circle.yml b/circle.yml
index 0ccf4b1..2820da5 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,30 +1,25 @@
-machine:
- services:
- - docker
- environment:
- IS_RELEASE: $(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
- BUILD_IMAGE: v2tec/gobuilder:0.1.0_go1.7.4-glide0.12.3-goreleaser0.6.2
+version: 2
+jobs:
+ build:
+ docker:
+ - image: v2tec/gobuilder:0.2.0_go1.7.4-glide0.12.3-goreleaser0.40.3-docker17.03.0
+ working_directory: /src
+ steps:
+ - checkout
+ - setup_remote_docker:
+ version: 17.03.0-ce
+ - run: git fetch --tags
+ - run: |
+ IS_RELEASE=$(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
+ /build.sh $IS_RELEASE
-dependencies:
- override:
- - git fetch --tags
- - docker pull $BUILD_IMAGE
-
-compile:
- override:
- - docker run -v "$PWD":/src -e GITHUB_TOKEN=$GITHUB_TOKEN $BUILD_IMAGE $IS_RELEASE $CIRCLE_BRANCH-$CIRCLE_SHA1
-
-test:
- override:
- - echo "Tests included in compile step."
-
-deployment:
- ci:
- branch: /.*/
- commands:
- - cp -r ./dist/* $CIRCLE_ARTIFACTS
- release:
- tag: /v[0-9]+(\.[0-9]+)*/
- owner: v2tec
- commands:
- - cp -r ./dist/* $CIRCLE_ARTIFACTS
+ - store_artifacts:
+ path: /src/dist/
+workflows:
+ version: 2
+ build-deploy:
+ jobs:
+ - build:
+ filters:
+ tags:
+ only: /.*/
\ No newline at end of file
diff --git a/dockerfile/amd64/Dockerfile b/dockerfile/amd64/Dockerfile
new file mode 100644
index 0000000..bc85457
--- /dev/null
+++ b/dockerfile/amd64/Dockerfile
@@ -0,0 +1,4 @@
+FROM centurylink/ca-certs
+LABEL "com.centurylinklabs.watchtower"="true"
+COPY watchtower /
+ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/armhf/Dockerfile b/dockerfile/armhf/Dockerfile
new file mode 100644
index 0000000..bc85457
--- /dev/null
+++ b/dockerfile/armhf/Dockerfile
@@ -0,0 +1,4 @@
+FROM centurylink/ca-certs
+LABEL "com.centurylinklabs.watchtower"="true"
+COPY watchtower /
+ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/goreleaser.yml b/goreleaser.yml
index 8b89760..fb82639 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -24,14 +24,8 @@ build:
# Archive customization
archive:
# You can change the name of the archive.
- # This is parsed with Golang template engine and the following variables
- # are available:
- # - BinaryName
- # - Version
- # - Os
- # - Arch
- # The default is `{{.BinaryName}}_{{.Os}}_{{.Arch}}`
- name_template: "{{.BinaryName}}_{{.Os}}_{{.Arch}}"
+ # This is parsed with Golang template engine and the following variables.
+ name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
# Archive format. Valid options are `tar.gz` and `zip`.
# Default is `zip`
@@ -54,3 +48,21 @@ archive:
# `README*` and `CHANGELOG*` (case-insensitive)
files:
- LICENSE.md
+
+dockers:
+ -
+ goos: linux
+ goarch: amd64
+ goarm: ''
+ binary: watchtower
+ image: v2tec/watchtower
+ dockerfile: dockerfile/amd64/Dockerfile
+ tag_template: "{{ .Version }}"
+ -
+ goos: linux
+ goarch: arm
+ goarm: 6
+ binary: watchtower
+ image: v2tec/watchtower
+ dockerfile: dockerfile/armhf/Dockerfile
+ tag_template: "armhf-{{ .Version }}"
\ No newline at end of file
From 6d3a3f7ff045299988a8e1c869f83f264e9f37ec Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 27 Dec 2017 15:37:55 +0100
Subject: [PATCH 031/761] Crosscompile for windows.
---
goreleaser.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/goreleaser.yml b/goreleaser.yml
index fb82639..1fd04fc 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -13,6 +13,7 @@ build:
# Defaults are darwin and linux
goos:
- linux
+ - windows
# GOARCH to build in.
# For more info refer to https://golang.org/doc/install/source#environment
@@ -43,6 +44,10 @@ archive:
darwin: macOS
linux: linux
+ format_overrides:
+ - goos: windows
+ format: zip
+
# Additional files you want to add to the archive.
# Defaults are any files matching `LICENCE*`, `LICENSE*`,
# `README*` and `CHANGELOG*` (case-insensitive)
From e948aa946fe2b7c014a31291e8b605f85f617c02 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 27 Dec 2017 17:57:36 +0100
Subject: [PATCH 032/761] Login to docker to publish docker images.
---
circle.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/circle.yml b/circle.yml
index 2820da5..f39fc9c 100644
--- a/circle.yml
+++ b/circle.yml
@@ -10,6 +10,7 @@ jobs:
version: 17.03.0-ce
- run: git fetch --tags
- run: |
+ docker login -u $DOCKER_USER -p $DOCKER_PASS
IS_RELEASE=$(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
/build.sh $IS_RELEASE
From 4ef9a7ab97f2e831dddd401cc6d7432af3bfa844 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 27 Dec 2017 19:15:50 +0100
Subject: [PATCH 033/761] Fix the version information output. Additionally
output the commit hash and the build date.
---
goreleaser.yml | 4 ----
main.go | 4 +++-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/goreleaser.yml b/goreleaser.yml
index 1fd04fc..4757aac 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -4,10 +4,6 @@ build:
# Default is `main.go`
main: ./main.go
- # Custom ldflags.
- # Default is `-s -w`
- ldflags: -s -w
-
# GOOS list to build in.
# For more info refer to https://golang.org/doc/install/source#environment
# Defaults are darwin and linux
diff --git a/main.go b/main.go
index bf9bd21..89a7f5f 100644
--- a/main.go
+++ b/main.go
@@ -21,6 +21,8 @@ import (
const DockerAPIMinVersion string = "1.24"
var version = "master"
+var commit = "unknown"
+var date = "unknown"
var (
client container.Client
@@ -37,7 +39,7 @@ func init() {
func main() {
app := cli.NewApp()
app.Name = "watchtower"
- app.Version = version
+ app.Version = version + " - " + commit + " - " + date
app.Usage = "Automatically update running Docker containers"
app.Before = before
app.Action = start
From b0dd07905ba2e7cdc25b3395597d3322c52d07dd Mon Sep 17 00:00:00 2001
From: cron410
Date: Fri, 29 Dec 2017 10:52:45 -0500
Subject: [PATCH 034/761] added example for Cron expression
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9edffa1..2cc54a4 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ docker run --rm v2tec/watchtower --help
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
-* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both.
+* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *" `
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
* `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
From a23f49f4442b774c0f7eccfc7faa2624c5305aad Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Mon, 1 Jan 2018 09:34:53 +0100
Subject: [PATCH 035/761] Do not send an email notification when no messages
have been logged.
---
notifications/email.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/notifications/email.go b/notifications/email.go
index 9fd8774..075aae4 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -87,10 +87,10 @@ func (e *emailTypeNotifier) StartNotification() {
}
func (e *emailTypeNotifier) SendNotification() {
- if e.entries != nil {
+ if e.entries != nil && len(e.entries) != 0 {
e.sendEntries(e.entries)
- e.entries = nil
}
+ e.entries = nil
}
func (e *emailTypeNotifier) Levels() []log.Level {
From 703dcb1a6d0304bc4ae75c7bb35389838f2f16c6 Mon Sep 17 00:00:00 2001
From: Max Schmitt
Date: Mon, 1 Jan 2018 14:32:09 +0100
Subject: [PATCH 036/761] Fixed badges layouts for consistency
And added goreportcard badge.
---
README.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 2cc54a4..36015bd 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
# Watchtower
-[](https://circleci.com/gh/v2tec/watchtower)
-[](https://godoc.org/github.com/v2tec/watchtower)
+
+[](https://circleci.com/gh/v2tec/watchtower)
+[](https://godoc.org/github.com/v2tec/watchtower)
[](https://microbadger.com/images/v2tec/watchtower "Get your own image badge on microbadger.com")
+[](https://goreportcard.com/report/github.com/v2tec/watchtower)
A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.
From c463241bc711f7ac5565f5fe11a6c2f3ebfc0b36 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Wed, 27 Dec 2017 20:25:49 +0100
Subject: [PATCH 037/761] SMTP port configurable through
`notification-email-server-port`. Defaults to 25.
---
README.md | 1 +
main.go | 6 ++++++
notifications/email.go | 6 +++++-
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9edffa1..02ad361 100644
--- a/README.md
+++ b/README.md
@@ -193,6 +193,7 @@ To receive notifications by email, the following command-line options, or their
* `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
* `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
* `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
+* `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
* `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
* `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
diff --git a/main.go b/main.go
index 89a7f5f..1fd015b 100644
--- a/main.go
+++ b/main.go
@@ -111,6 +111,12 @@ func main() {
Usage: "SMTP server to send notification e-mails through",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER",
},
+ cli.IntFlag{
+ Name: "notification-email-server-port",
+ Usage: "SMTP server port to send notification e-mails through",
+ Value: 25,
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT",
+ },
cli.StringFlag{
Name: "notification-email-server-user",
Usage: "SMTP server user for sending notifications",
diff --git a/notifications/email.go b/notifications/email.go
index 9fd8774..c92a58f 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -6,6 +6,8 @@ import (
"net/smtp"
"os"
+ "strconv"
+
log "github.com/Sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -22,6 +24,7 @@ const (
type emailTypeNotifier struct {
From, To string
Server, User, Password string
+ Port int
entries []*log.Entry
}
@@ -32,6 +35,7 @@ func newEmailNotifier(c *cli.Context) typeNotifier {
Server: c.GlobalString("notification-email-server"),
User: c.GlobalString("notification-email-server-user"),
Password: c.GlobalString("notification-email-server-password"),
+ Port: c.GlobalInt("notification-email-server-port"),
}
log.AddHook(n)
@@ -72,7 +76,7 @@ func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
msg := e.buildMessage(entries)
go func() {
auth := smtp.PlainAuth("", e.User, e.Password, e.Server)
- err := smtp.SendMail(e.Server+":25", auth, e.From, []string{e.To}, msg)
+ err := smtp.SendMail(e.Server+":"+strconv.Itoa(e.Port), auth, e.From, []string{e.To}, msg)
if err != nil {
// Use fmt so it doesn't trigger another email.
fmt.Println("Failed to send notification email: ", err)
From 598b5ebcedde312562d796093dfa916ca2fa1f85 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 5 Jan 2018 19:52:49 +0100
Subject: [PATCH 038/761] Push semver containers during a release build.
---
circle.yml | 4 ++-
dockerfile/push_containers.sh | 52 +++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
create mode 100644 dockerfile/push_containers.sh
diff --git a/circle.yml b/circle.yml
index f39fc9c..33b1905 100644
--- a/circle.yml
+++ b/circle.yml
@@ -12,7 +12,9 @@ jobs:
- run: |
docker login -u $DOCKER_USER -p $DOCKER_PASS
IS_RELEASE=$(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
- /build.sh $IS_RELEASE
+ /build.sh $IS_RELEASE || exit 1
+ chmod 755 /src/dockerfile/push_containers.sh
+ if [ "$CIRCLE_TAG" != "" ] ; then /src/dockerfile/push_containers.sh $CIRCLE_TAG; fi;
- store_artifacts:
path: /src/dist/
diff --git a/dockerfile/push_containers.sh b/dockerfile/push_containers.sh
new file mode 100644
index 0000000..fe082bb
--- /dev/null
+++ b/dockerfile/push_containers.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+PROGNAME=$(basename $0)
+VERSION_BUILD=$1
+
+function error_exit
+{
+ echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
+ exit 1
+}
+
+if [ "$1" = "" ]; then
+ error_exit "Please provide version as first argument."
+fi
+
+SEMVER=${VERSION_BUILD#*v}
+VERSION=`echo $SEMVER | awk '{split($0,a,"."); print a[1]}'`
+BUILD=`echo $SEMVER | awk '{split($0,a,"."); print a[2]}'`
+PATCH=`echo $SEMVER | awk '{split($0,a,"."); print a[3]}'`
+
+if [ "${VERSION}" = "" ]; then
+ echo "Please provide a semantic version."
+ exit 1
+fi
+
+if [ "${BUILD}" = "" ]; then
+ BUILD='0'
+fi
+
+if [ "${PATCH}" = "" ]; then
+ PATCH='0'
+fi
+
+push_docker() {
+ echo " -> push $1 $2"
+ docker tag $1 $2 || exit 1
+ docker push $2 || exit 1
+}
+
+push_all() {
+ IMAGE_NAME_VERSION=${1}${VERSION}.${BUILD}.${PATCH}
+ echo "Pulling $IMAGE_NAME_VERSION..."
+ docker pull ${IMAGE_NAME_VERSION} || exit 1
+ echo "Pushing $IMAGE_NAME_VERSION..."
+ push_docker ${IMAGE_NAME_VERSION} ${1}${VERSION}.${BUILD}
+ push_docker ${IMAGE_NAME_VERSION} ${1}${VERSION}
+ push_docker ${IMAGE_NAME_VERSION} ${1}latest
+}
+
+IMAGE_NAME=v2tec/watchtower
+push_all ${IMAGE_NAME}:
+push_all ${IMAGE_NAME}:armhf-
\ No newline at end of file
From 23275d44a2a6760c3a87d529ecbe753d91616ac3 Mon Sep 17 00:00:00 2001
From: fomk
Date: Fri, 16 Feb 2018 11:24:43 +0200
Subject: [PATCH 039/761] Update email.go
Added Date header
---
notifications/email.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/notifications/email.go b/notifications/email.go
index c675a11..b12176c 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/smtp"
"os"
+ "time"
"strconv"
@@ -54,10 +55,13 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
// We don't use fields in watchtower, so don't bother sending them.
}
+ t := time.Now()
+
header := make(map[string]string)
header["From"] = e.From
header["To"] = e.To
header["Subject"] = emailSubject
+ header["Date"] = t.Format(time.RFC1123)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
From 462760fa7d9579c0acf7937f352a5dc09240c06d Mon Sep 17 00:00:00 2001
From: fomk
Date: Fri, 16 Feb 2018 12:44:34 +0200
Subject: [PATCH 040/761] Update email.go
Changed date format to RFC1123Z
---
notifications/email.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/notifications/email.go b/notifications/email.go
index b12176c..f6d252c 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -61,7 +61,7 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
header["From"] = e.From
header["To"] = e.To
header["Subject"] = emailSubject
- header["Date"] = t.Format(time.RFC1123)
+ header["Date"] = t.Format(time.RFC1123Z)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
From 714e30f532a9f574d78336ceba2207f2508eff02 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Sun, 7 Jan 2018 10:54:56 +0100
Subject: [PATCH 041/761] Output error message when a pull failed.
---
actions/update.go | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/actions/update.go b/actions/update.go
index b41d34c..15cd017 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -45,8 +45,7 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
for i, container := range containers {
stale, err := client.IsContainerStale(container)
if err != nil {
- log.Infof("Unable to update container %s. Proceeding to next.", containers[i].Name())
- log.Debug(err)
+ log.Infof("Unable to update container %s, err='%s'. Proceeding to next.", containers[i].Name(), err)
stale = false
}
containers[i].Stale = stale
From 5463256aa2203f8594ad7343a41269823aeead35 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Mon, 22 Jan 2018 20:36:32 +0100
Subject: [PATCH 042/761] Possibility to disable the TLS verify for sending
mails.
---
LICENSE.md | 5 +++
README.md | 1 +
main.go | 41 ++++++++++++++---------
notifications/email.go | 16 +++++----
notifications/smtp.go | 76 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 116 insertions(+), 23 deletions(-)
create mode 100644 notifications/smtp.go
diff --git a/LICENSE.md b/LICENSE.md
index 3a3284a..474d999 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -199,3 +199,8 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+
+Watchtower contains code that is licensed under a BSD-license:
+ - Copyright (c) 2009 The Go Authors. All rights reserved.
+
+ For details see https://golang.org/LICENSE
diff --git a/README.md b/README.md
index bb1d1cc..8b4a3f3 100644
--- a/README.md
+++ b/README.md
@@ -195,6 +195,7 @@ To receive notifications by email, the following command-line options, or their
* `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
* `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
* `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
+* `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
* `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
* `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
* `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
diff --git a/main.go b/main.go
index 1fd015b..fd74fcc 100644
--- a/main.go
+++ b/main.go
@@ -91,40 +91,49 @@ func main() {
Usage: "enable debug mode with verbose logging",
},
cli.StringSliceFlag{
- Name: "notifications",
- Value: &cli.StringSlice{},
- Usage: "notification types to send (valid: email)",
+ Name: "notifications",
+ Value: &cli.StringSlice{},
+ Usage: "notification types to send (valid: email)",
EnvVar: "WATCHTOWER_NOTIFICATIONS",
},
cli.StringFlag{
- Name: "notification-email-from",
- Usage: "Address to send notification e-mails from",
+ Name: "notification-email-from",
+ Usage: "Address to send notification e-mails from",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_FROM",
},
cli.StringFlag{
- Name: "notification-email-to",
- Usage: "Address to send notification e-mails to",
+ Name: "notification-email-to",
+ Usage: "Address to send notification e-mails to",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_TO",
},
cli.StringFlag{
- Name: "notification-email-server",
- Usage: "SMTP server to send notification e-mails through",
+ Name: "notification-email-server",
+ Usage: "SMTP server to send notification e-mails through",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER",
},
cli.IntFlag{
- Name: "notification-email-server-port",
- Usage: "SMTP server port to send notification e-mails through",
+ Name: "notification-email-server-port",
+ Usage: "SMTP server port to send notification e-mails through",
Value: 25,
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT",
},
+ cli.BoolFlag{
+ Name: "notification-email-server-tls-skip-verify",
+ Usage: "Controls whether watchtower verifies the SMTP server's certificate chain and host name. " +
+ "If set, TLS accepts any certificate " +
+ "presented by the server and any host name in that certificate. " +
+ "In this mode, TLS is susceptible to man-in-the-middle attacks. " +
+ "This should be used only for testing.",
+ EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY",
+ },
cli.StringFlag{
- Name: "notification-email-server-user",
- Usage: "SMTP server user for sending notifications",
+ Name: "notification-email-server-user",
+ Usage: "SMTP server user for sending notifications",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER",
},
cli.StringFlag{
- Name: "notification-email-server-password",
- Usage: "SMTP server password for sending notifications",
+ Name: "notification-email-server-password",
+ Usage: "SMTP server password for sending notifications",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
},
}
@@ -180,7 +189,7 @@ func start(c *cli.Context) error {
scheduleSpec,
func() {
select {
- case v := <- tryLockSem:
+ case v := <-tryLockSem:
defer func() { tryLockSem <- v }()
notifier.StartNotification()
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
diff --git a/notifications/email.go b/notifications/email.go
index f6d252c..bb63329 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -26,17 +26,19 @@ type emailTypeNotifier struct {
From, To string
Server, User, Password string
Port int
+ tlsSkipVerify bool
entries []*log.Entry
}
func newEmailNotifier(c *cli.Context) typeNotifier {
n := &emailTypeNotifier{
- From: c.GlobalString("notification-email-from"),
- To: c.GlobalString("notification-email-to"),
- Server: c.GlobalString("notification-email-server"),
- User: c.GlobalString("notification-email-server-user"),
- Password: c.GlobalString("notification-email-server-password"),
- Port: c.GlobalInt("notification-email-server-port"),
+ From: c.GlobalString("notification-email-from"),
+ To: c.GlobalString("notification-email-to"),
+ Server: c.GlobalString("notification-email-server"),
+ User: c.GlobalString("notification-email-server-user"),
+ Password: c.GlobalString("notification-email-server-password"),
+ Port: c.GlobalInt("notification-email-server-port"),
+ tlsSkipVerify: c.GlobalBool("notification-email-server-tls-skip-verify"),
}
log.AddHook(n)
@@ -80,7 +82,7 @@ func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
msg := e.buildMessage(entries)
go func() {
auth := smtp.PlainAuth("", e.User, e.Password, e.Server)
- err := smtp.SendMail(e.Server+":"+strconv.Itoa(e.Port), auth, e.From, []string{e.To}, msg)
+ err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, []string{e.To}, msg)
if err != nil {
// Use fmt so it doesn't trigger another email.
fmt.Println("Failed to send notification email: ", err)
diff --git a/notifications/smtp.go b/notifications/smtp.go
new file mode 100644
index 0000000..d0f07f8
--- /dev/null
+++ b/notifications/smtp.go
@@ -0,0 +1,76 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license.
+package notifications
+
+import (
+ "crypto/tls"
+ "net"
+ "net/smtp"
+)
+
+// SendMail connects to the server at addr, switches to TLS if
+// possible, authenticates with the optional mechanism a if possible,
+// and then sends an email from address from, to addresses to, with
+// message msg.
+// The addr must include a port, as in "mail.example.com:smtp".
+//
+// The addresses in the to parameter are the SMTP RCPT addresses.
+//
+// The msg parameter should be an RFC 822-style email with headers
+// first, a blank line, and then the message body. The lines of msg
+// should be CRLF terminated. The msg headers should usually include
+// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
+// messages is accomplished by including an email address in the to
+// parameter but not including it in the msg headers.
+//
+// The SendMail function and the net/smtp package are low-level
+// mechanisms and provide no support for DKIM signing, MIME
+// attachments (see the mime/multipart package), or other mail
+// functionality. Higher-level packages exist outside of the standard
+// library.
+func SendMail(addr string, insecureSkipVerify bool, a smtp.Auth, from string, to []string, msg []byte) error {
+ c, err := smtp.Dial(addr)
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+ if err = c.Hello("localHost"); err != nil {
+ return err
+ }
+ if ok, _ := c.Extension("STARTTLS"); ok {
+ serverName, _, _ := net.SplitHostPort(addr)
+ config := &tls.Config{ServerName: serverName, InsecureSkipVerify: insecureSkipVerify}
+ if err = c.StartTLS(config); err != nil {
+ return err
+ }
+ }
+ if a != nil {
+ if ok, _ := c.Extension("AUTH"); ok {
+ if err = c.Auth(a); err != nil {
+ return err
+ }
+ }
+ }
+ if err = c.Mail(from); err != nil {
+ return err
+ }
+ for _, addr := range to {
+ if err = c.Rcpt(addr); err != nil {
+ return err
+ }
+ }
+ w, err := c.Data()
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(msg)
+ if err != nil {
+ return err
+ }
+ err = w.Close()
+ if err != nil {
+ return err
+ }
+ return c.Quit()
+}
From 28dff65282404dd224b94e592da2f49788784d22 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Mon, 22 Jan 2018 20:56:18 +0100
Subject: [PATCH 043/761] mail: only authenticate if user has been set.
---
notifications/email.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/notifications/email.go b/notifications/email.go
index bb63329..6850ca9 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -81,7 +81,10 @@ func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
// Do the sending in a separate goroutine so we don't block the main process.
msg := e.buildMessage(entries)
go func() {
- auth := smtp.PlainAuth("", e.User, e.Password, e.Server)
+ var auth smtp.Auth
+ if e.User != "" {
+ auth = smtp.PlainAuth("", e.User, e.Password, e.Server)
+ }
err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, []string{e.To}, msg)
if err != nil {
// Use fmt so it doesn't trigger another email.
From 09f047ab8bebb10514ed0135bcb26cb19fa484f7 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Tue, 23 Jan 2018 20:05:18 +0100
Subject: [PATCH 044/761] Send mails that correspond to RFC2045 with a base64
line limit of 76 characters.
---
notifications/email.go | 7 ++++++-
notifications/util.go | 24 ++++++++++++++++++++++++
2 files changed, 30 insertions(+), 1 deletion(-)
create mode 100644 notifications/util.go
diff --git a/notifications/email.go b/notifications/email.go
index 6850ca9..5380f7f 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -72,7 +72,12 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
for k, v := range header {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
- message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
+
+ encodedBody := base64.StdEncoding.EncodeToString([]byte(body))
+ //RFC 2045 base64 encoding demands line no longer than 76 characters.
+ for _, line := range SplitSubN(encodedBody, 76) {
+ message += "\r\n" + line
+ }
return []byte(message)
}
diff --git a/notifications/util.go b/notifications/util.go
new file mode 100644
index 0000000..5764341
--- /dev/null
+++ b/notifications/util.go
@@ -0,0 +1,24 @@
+package notifications
+
+import "bytes"
+
+// SplitSubN splits a string into a list of string with each having
+// a maximum number of characters n
+func SplitSubN(s string, n int) []string {
+ sub := ""
+ subs := []string{}
+
+ runes := bytes.Runes([]byte(s))
+ l := len(runes)
+ for i, r := range runes {
+ sub = sub + string(r)
+ if (i+1)%n == 0 {
+ subs = append(subs, sub)
+ sub = ""
+ } else if (i + 1) == l {
+ subs = append(subs, sub)
+ }
+ }
+
+ return subs
+}
From d989254b1aa4da28ea2fe64fde41dd69bab8bda7 Mon Sep 17 00:00:00 2001
From: ubergesundheit
Date: Mon, 27 Nov 2017 12:03:00 +0100
Subject: [PATCH 045/761] change upper case S in sirupsen to lower case to
avoid build error
---
actions/update.go | 2 +-
container/client.go | 2 +-
container/trust.go | 2 +-
glide.yaml | 2 +-
main.go | 2 +-
notifications/email.go | 2 +-
notifications/notifier.go | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/actions/update.go b/actions/update.go
index 15cd017..1f61af5 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -4,7 +4,7 @@ import (
"math/rand"
"time"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/v2tec/watchtower/container"
)
diff --git a/container/client.go b/container/client.go
index afa9a33..340c51b 100644
--- a/container/client.go
+++ b/container/client.go
@@ -5,7 +5,7 @@ import (
"io/ioutil"
"time"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client"
diff --git a/container/trust.go b/container/trust.go
index a472649..90ba335 100644
--- a/container/trust.go
+++ b/container/trust.go
@@ -5,7 +5,7 @@ import (
"os"
"strings"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/reference"
"github.com/docker/docker/cli/command"
diff --git a/glide.yaml b/glide.yaml
index 8478cfa..6a96785 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,6 +1,6 @@
package: github.com/v2tec/watchtower
import:
-- package: github.com/Sirupsen/logrus
+- package: github.com/sirupsen/logrus
version: ~0.11.x
- package: github.com/docker/docker
version: ~1.13.x
diff --git a/main.go b/main.go
index fd74fcc..5c69340 100644
--- a/main.go
+++ b/main.go
@@ -8,7 +8,7 @@ import (
"strconv"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/robfig/cron"
"github.com/urfave/cli"
"github.com/v2tec/watchtower/actions"
diff --git a/notifications/email.go b/notifications/email.go
index 5380f7f..54a2f9f 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -9,7 +9,7 @@ import (
"strconv"
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
diff --git a/notifications/notifier.go b/notifications/notifier.go
index a73a63e..b202c65 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -1,7 +1,7 @@
package notifications
import (
- log "github.com/Sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
From fd7f8a40ed480e019eae53f05295fb6abe46d334 Mon Sep 17 00:00:00 2001
From: ubergesundheit
Date: Mon, 27 Nov 2017 12:04:08 +0100
Subject: [PATCH 046/761] add slackrus slack notifications
---
README.md | 22 ++++++++++++++++++++++
glide.lock | 20 ++++++++++++--------
glide.yaml | 2 ++
main.go | 21 +++++++++++++++++++--
notifications/notifier.go | 2 ++
notifications/slack.go | 38 ++++++++++++++++++++++++++++++++++++++
6 files changed, 95 insertions(+), 10 deletions(-)
create mode 100644 notifications/slack.go
diff --git a/README.md b/README.md
index 8b4a3f3..ba4d0ec 100644
--- a/README.md
+++ b/README.md
@@ -189,6 +189,9 @@ Watchtower can send notifications when containers are updated. Notifications are
The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
* `email` to send notifications via e-mail
+* `slack` to send notifications through a Slack webhook
+
+### Notifications via E-Mail
To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
@@ -215,3 +218,22 @@ docker run -d \
v2tec/watchtower
```
+### Notifications through Slack webhook
+
+To receive notifications in Slack, add `slack` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
+
+Additionally, you should set the Slack webhook url using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable.
+
+By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=slack \
+ -e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
+ -e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
+ v2tec/watchtower
+```
diff --git a/glide.lock b/glide.lock
index 7ae50d9..55cd59e 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: 9ddd729b207d71ce16ae9a0282ac87a046b9161ac4f15b108e86a03367172516
-updated: 2017-01-24T20:45:43.1914053+01:00
+hash: c0e0c69383b9b665e92c40a5ea69632ea846a645f44654411137c75c5ef5017c
+updated: 2017-11-27T11:34:21.283697431+01:00
imports:
- name: github.com/Azure/go-ansiterm
version: 388960b655244e76e24c75f48631564eaefade62
@@ -25,7 +25,7 @@ imports:
- registry/storage/cache/memory
- uuid
- name: github.com/docker/docker
- version: 49bf474f9ed7ce7143a59d1964ff7b7fd9b52178
+ version: 092cba3727bb9b4a2f0e922cd6c0f93ea270e363
subpackages:
- api
- api/server/httputils
@@ -110,13 +110,16 @@ imports:
version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
+- name: github.com/johntdyer/slack-go
+ version: 88ef3038545fa084d81f36e4a6943d112cdd74f6
+- name: github.com/johntdyer/slackrus
+ version: 3992f319fd0ac349483279ef74742cd787841cba
- name: github.com/mattn/go-shellwords
version: f4e566c536cf69158e808ec28ef4182a37fdc981
- name: github.com/Microsoft/go-winio
version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
- name: github.com/opencontainers/runc
- version: 2f7393a47307a16f8cee44a37b262e8b81021e3e
- repo: https://github.com/docker/runc.git
+ version: e8149af2910850602cae998dede29e630d1b8ee1
subpackages:
- libcontainer/configs
- libcontainer/devices
@@ -135,10 +138,11 @@ imports:
- name: github.com/robfig/cron
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
- name: github.com/Sirupsen/logrus
- version: d26492970760ca5d33129d2d799e34be5c4782eb
+ version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
+- name: github.com/sirupsen/logrus
+ version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
- name: github.com/spf13/cobra
- version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
- repo: https://github.com/dnephin/cobra.git
+ version: 1be1d2841c773c01bee8289f55f7463b6e2c2539
- name: github.com/spf13/pflag
version: dabebe21bf790f782ea4c7bbd2efc430de182afd
- name: github.com/stretchr/objx
diff --git a/glide.yaml b/glide.yaml
index 6a96785..24609b9 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -26,3 +26,5 @@ import:
- context
- package: github.com/robfig/cron
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
+- package: github.com/johntdyer/slackrus
+ version: 3992f31
diff --git a/main.go b/main.go
index 5c69340..38b1269 100644
--- a/main.go
+++ b/main.go
@@ -8,8 +8,8 @@ import (
"strconv"
- log "github.com/sirupsen/logrus"
"github.com/robfig/cron"
+ log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
"github.com/v2tec/watchtower/actions"
"github.com/v2tec/watchtower/container"
@@ -93,7 +93,7 @@ func main() {
cli.StringSliceFlag{
Name: "notifications",
Value: &cli.StringSlice{},
- Usage: "notification types to send (valid: email)",
+ Usage: "notification types to send (valid: email, slack)",
EnvVar: "WATCHTOWER_NOTIFICATIONS",
},
cli.StringFlag{
@@ -136,6 +136,23 @@ func main() {
Usage: "SMTP server password for sending notifications",
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
},
+ cli.StringFlag{
+ Name: "notification-slack-hook-url",
+ Usage: "The Slack Hook URL to send notifications to",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL",
+ },
+ cli.StringFlag{
+ Name: "notification-slack-identifier",
+ Usage: "A string which will be used to identify the messages coming from this watchtower instance. Default if omitted is \"watchtower\"",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER",
+ Value: "watchtower",
+ },
+ cli.StringFlag{
+ Name: "notification-slack-level",
+ Usage: "The log level used for sending notifications through Slack. Default if omitted is \"info\". Possible values: \"panic\",\"fatal\",\"error\",\"warn\",\"info\" or \"debug\"",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_LEVEL",
+ Value: "info",
+ },
}
if err := app.Run(os.Args); err != nil {
diff --git a/notifications/notifier.go b/notifications/notifier.go
index b202c65..ab635bb 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -26,6 +26,8 @@ func NewNotifier(c *cli.Context) *Notifier {
switch t {
case emailType:
tn = newEmailNotifier(c)
+ case slackType:
+ tn = newSlackNotifier(c)
default:
log.Fatalf("Unknown notification type %q", t)
}
diff --git a/notifications/slack.go b/notifications/slack.go
new file mode 100644
index 0000000..3b17354
--- /dev/null
+++ b/notifications/slack.go
@@ -0,0 +1,38 @@
+package notifications
+
+import (
+ "github.com/johntdyer/slackrus"
+ log "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+const (
+ slackType = "slack"
+)
+
+type slackTypeNotifier struct {
+ slackrus.SlackrusHook
+}
+
+func newSlackNotifier(c *cli.Context) typeNotifier {
+ logLevel, err := log.ParseLevel(c.GlobalString("notification-slack-level"))
+ if err != nil {
+ log.Fatalf("Slack notifications: %s", err.Error())
+ }
+
+ n := &slackTypeNotifier{
+ SlackrusHook: slackrus.SlackrusHook{
+ HookURL: c.GlobalString("notification-slack-hook-url"),
+ Username: c.GlobalString("notification-slack-identifier"),
+ AcceptedLevels: slackrus.LevelThreshold(logLevel),
+ },
+ }
+
+ log.AddHook(n)
+
+ return n
+}
+
+func (s *slackTypeNotifier) StartNotification() {}
+
+func (s *slackTypeNotifier) SendNotification() {}
From 4ac08dfb30f66c87dba8579f5d821d401f89eadd Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Thu, 1 Mar 2018 12:53:48 +0100
Subject: [PATCH 047/761] glide alias Sirupsen to sirupsen
---
glide.lock | 17 ++++++++++-------
glide.yaml | 3 +++
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/glide.lock b/glide.lock
index 55cd59e..a4dcf44 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: c0e0c69383b9b665e92c40a5ea69632ea846a645f44654411137c75c5ef5017c
-updated: 2017-11-27T11:34:21.283697431+01:00
+hash: 23bcd62f9352c61d3aebc85a59f7ebbfaf80bc0201f45037cdea65820673ddeb
+updated: 2018-03-01T13:21:55.8472118+01:00
imports:
- name: github.com/Azure/go-ansiterm
version: 388960b655244e76e24c75f48631564eaefade62
@@ -91,7 +91,7 @@ imports:
- client
- credentials
- name: github.com/docker/go-connections
- version: 4ccf312bf1d35e5dbda654e57a9be4c3f3cd0366
+ version: ecb4cb2dd420ada7df7f2593d6c25441f65f69f2
subpackages:
- nat
- sockets
@@ -111,15 +111,16 @@ imports:
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/johntdyer/slack-go
- version: 88ef3038545fa084d81f36e4a6943d112cdd74f6
+ version: 95fac1160b220c5abcf8b0ef88e9c3cb213c09f4
- name: github.com/johntdyer/slackrus
version: 3992f319fd0ac349483279ef74742cd787841cba
- name: github.com/mattn/go-shellwords
version: f4e566c536cf69158e808ec28ef4182a37fdc981
- name: github.com/Microsoft/go-winio
- version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
+ version: fff283ad5116362ca252298cfc9b95828956d85d
- name: github.com/opencontainers/runc
- version: e8149af2910850602cae998dede29e630d1b8ee1
+ version: 9df8b306d01f59d3a8029be411de015b7304dd8f
+ repo: https://github.com/docker/runc.git
subpackages:
- libcontainer/configs
- libcontainer/devices
@@ -139,10 +140,12 @@ imports:
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
- name: github.com/Sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
+ repo: https://github.com/sirupsen/logrus.git
- name: github.com/sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
- name: github.com/spf13/cobra
- version: 1be1d2841c773c01bee8289f55f7463b6e2c2539
+ version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
+ repo: https://github.com/dnephin/cobra.git
- name: github.com/spf13/pflag
version: dabebe21bf790f782ea4c7bbd2efc430de182afd
- name: github.com/stretchr/objx
diff --git a/glide.yaml b/glide.yaml
index 24609b9..20ef77d 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -2,6 +2,9 @@ package: github.com/v2tec/watchtower
import:
- package: github.com/sirupsen/logrus
version: ~0.11.x
+- package: github.com/Sirupsen/logrus
+ repo: https://github.com/sirupsen/logrus.git
+ version: ~0.11.x
- package: github.com/docker/docker
version: ~1.13.x
subpackages:
From e1ead2f46f597ceb7e353f621e724ee613de52e5 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 13:08:40 +0100
Subject: [PATCH 048/761] Made the notification level flag global for all
notification types.
---
README.md | 4 ++++
main.go | 12 ++++++------
notifications/email.go | 13 ++++---------
notifications/notifier.go | 12 ++++++++++--
notifications/slack.go | 9 ++-------
5 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/README.md b/README.md
index ba4d0ec..224f158 100644
--- a/README.md
+++ b/README.md
@@ -191,6 +191,10 @@ The types of notifications to send are passed via the comma-separated option `--
* `email` to send notifications via e-mail
* `slack` to send notifications through a Slack webhook
+### Settings
+
+* `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
+
### Notifications via E-Mail
To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
diff --git a/main.go b/main.go
index 38b1269..962ab33 100644
--- a/main.go
+++ b/main.go
@@ -96,6 +96,12 @@ func main() {
Usage: "notification types to send (valid: email, slack)",
EnvVar: "WATCHTOWER_NOTIFICATIONS",
},
+ cli.StringFlag{
+ Name: "notifications-level",
+ Usage: "The log level used for sending notifications. Possible values: \"panic\", \"fatal\", \"error\", \"warn\", \"info\" or \"debug\"",
+ EnvVar: "WATCHTOWER_NOTIFICATIONS_LEVEL",
+ Value: "info",
+ },
cli.StringFlag{
Name: "notification-email-from",
Usage: "Address to send notification e-mails from",
@@ -147,12 +153,6 @@ func main() {
EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER",
Value: "watchtower",
},
- cli.StringFlag{
- Name: "notification-slack-level",
- Usage: "The log level used for sending notifications through Slack. Default if omitted is \"info\". Possible values: \"panic\",\"fatal\",\"error\",\"warn\",\"info\" or \"debug\"",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_LEVEL",
- Value: "info",
- },
}
if err := app.Run(os.Args); err != nil {
diff --git a/notifications/email.go b/notifications/email.go
index 54a2f9f..5f84ca5 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -28,9 +28,10 @@ type emailTypeNotifier struct {
Port int
tlsSkipVerify bool
entries []*log.Entry
+ logLevels []log.Level
}
-func newEmailNotifier(c *cli.Context) typeNotifier {
+func newEmailNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
n := &emailTypeNotifier{
From: c.GlobalString("notification-email-from"),
To: c.GlobalString("notification-email-to"),
@@ -39,6 +40,7 @@ func newEmailNotifier(c *cli.Context) typeNotifier {
Password: c.GlobalString("notification-email-server-password"),
Port: c.GlobalInt("notification-email-server-port"),
tlsSkipVerify: c.GlobalBool("notification-email-server-tls-skip-verify"),
+ logLevels: acceptedLogLevels,
}
log.AddHook(n)
@@ -112,14 +114,7 @@ func (e *emailTypeNotifier) SendNotification() {
}
func (e *emailTypeNotifier) Levels() []log.Level {
- // TODO: Make this configurable.
- return []log.Level{
- log.PanicLevel,
- log.FatalLevel,
- log.ErrorLevel,
- log.WarnLevel,
- log.InfoLevel,
- }
+ return e.logLevels
}
func (e *emailTypeNotifier) Fire(entry *log.Entry) error {
diff --git a/notifications/notifier.go b/notifications/notifier.go
index ab635bb..274811b 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -1,6 +1,7 @@
package notifications
import (
+ "github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -19,15 +20,22 @@ type Notifier struct {
func NewNotifier(c *cli.Context) *Notifier {
n := &Notifier{}
+ logLevel, err := log.ParseLevel(c.GlobalString("notifications-level"))
+ if err != nil {
+ log.Fatalf("Notifications invalid log level: %s", err.Error())
+ }
+
+ acceptedLogLevels := slackrus.LevelThreshold(logLevel)
+
// Parse types and create notifiers.
types := c.GlobalStringSlice("notifications")
for _, t := range types {
var tn typeNotifier
switch t {
case emailType:
- tn = newEmailNotifier(c)
+ tn = newEmailNotifier(c, acceptedLogLevels)
case slackType:
- tn = newSlackNotifier(c)
+ tn = newSlackNotifier(c, acceptedLogLevels)
default:
log.Fatalf("Unknown notification type %q", t)
}
diff --git a/notifications/slack.go b/notifications/slack.go
index 3b17354..dd44f29 100644
--- a/notifications/slack.go
+++ b/notifications/slack.go
@@ -14,17 +14,12 @@ type slackTypeNotifier struct {
slackrus.SlackrusHook
}
-func newSlackNotifier(c *cli.Context) typeNotifier {
- logLevel, err := log.ParseLevel(c.GlobalString("notification-slack-level"))
- if err != nil {
- log.Fatalf("Slack notifications: %s", err.Error())
- }
-
+func newSlackNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{
HookURL: c.GlobalString("notification-slack-hook-url"),
Username: c.GlobalString("notification-slack-identifier"),
- AcceptedLevels: slackrus.LevelThreshold(logLevel),
+ AcceptedLevels: acceptedLogLevels,
},
}
From b15df25fc05fdc4896e425750fa1512e8efb018d Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 14:23:57 +0100
Subject: [PATCH 049/761] Build containers from scratch and use ca-certificates
and zoneinfo from latest alpine.
---
dockerfile/amd64/Dockerfile | 18 +++++++++++++++++-
dockerfile/armhf/Dockerfile | 18 +++++++++++++++++-
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/dockerfile/amd64/Dockerfile b/dockerfile/amd64/Dockerfile
index bc85457..13b19e8 100644
--- a/dockerfile/amd64/Dockerfile
+++ b/dockerfile/amd64/Dockerfile
@@ -1,4 +1,20 @@
-FROM centurylink/ca-certs
+#
+# Alpine image to get some needed data
+#
+FROM alpine:latest as alpine
+RUN apk add --no-cache \
+ ca-certificates \
+ tzdata
+
+#
+# Image
+#
+FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other containers
+COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
+
COPY watchtower /
ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/armhf/Dockerfile b/dockerfile/armhf/Dockerfile
index bc85457..13b19e8 100644
--- a/dockerfile/armhf/Dockerfile
+++ b/dockerfile/armhf/Dockerfile
@@ -1,4 +1,20 @@
-FROM centurylink/ca-certs
+#
+# Alpine image to get some needed data
+#
+FROM alpine:latest as alpine
+RUN apk add --no-cache \
+ ca-certificates \
+ tzdata
+
+#
+# Image
+#
+FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other containers
+COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
+
COPY watchtower /
ENTRYPOINT ["/watchtower"]
\ No newline at end of file
From 5a67cc50a3d07cb4261738cee7988941e32f71d6 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 15:49:14 +0100
Subject: [PATCH 050/761] update to build image with docker 17.05.0-ce and
latest goreleaser
---
circle.yml | 4 ++--
goreleaser.yml | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/circle.yml b/circle.yml
index 33b1905..6cbcc39 100644
--- a/circle.yml
+++ b/circle.yml
@@ -2,12 +2,12 @@ version: 2
jobs:
build:
docker:
- - image: v2tec/gobuilder:0.2.0_go1.7.4-glide0.12.3-goreleaser0.40.3-docker17.03.0
+ - image: v2tec/gobuilder:0.5.0_go1.7.4-glide0.12.3-goreleaser0.59.0-docker17.05.0
working_directory: /src
steps:
- checkout
- setup_remote_docker:
- version: 17.03.0-ce
+ version: 17.05.0-ce
- run: git fetch --tags
- run: |
docker login -u $DOCKER_USER -p $DOCKER_PASS
diff --git a/goreleaser.yml b/goreleaser.yml
index 4757aac..0d457ec 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -58,7 +58,8 @@ dockers:
binary: watchtower
image: v2tec/watchtower
dockerfile: dockerfile/amd64/Dockerfile
- tag_template: "{{ .Version }}"
+ tag_templates:
+ - '{{ .Version }}'
-
goos: linux
goarch: arm
@@ -66,4 +67,5 @@ dockers:
binary: watchtower
image: v2tec/watchtower
dockerfile: dockerfile/armhf/Dockerfile
- tag_template: "armhf-{{ .Version }}"
\ No newline at end of file
+ tag_templates:
+ - 'armhf-{{ .Version }}'
\ No newline at end of file
From 6197d9663566b1dc0a723cebb1e0562149d9c7aa Mon Sep 17 00:00:00 2001
From: Robotex
Date: Fri, 24 Mar 2017 19:39:53 +0100
Subject: [PATCH 051/761] Add --stop-timeout parameter
---
README.md | 1 +
actions/update.go | 5 ++---
main.go | 13 ++++++++++++-
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 224f158..8b3b260 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,7 @@ docker run --rm v2tec/watchtower --help
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *" `
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
+* `--stop-timeout` Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
* `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
diff --git a/actions/update.go b/actions/update.go
index 1f61af5..3866d4b 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -10,7 +10,6 @@ import (
var (
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
- waitTime = 10 * time.Second
)
func allContainersFilter(container.Container) bool { return true }
@@ -34,7 +33,7 @@ func containerFilter(names []string) container.Filter {
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
-func Update(client container.Client, names []string, cleanup bool, noRestart bool) error {
+func Update(client container.Client, names []string, cleanup bool, noRestart bool, timeout time.Duration) error {
log.Debug("Checking containers for updated images")
containers, err := client.ListContainers(containerFilter(names))
@@ -67,7 +66,7 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
}
if container.Stale {
- if err := client.StopContainer(container, waitTime); err != nil {
+ if err := client.StopContainer(container, timeout); err != nil {
log.Error(err)
}
}
diff --git a/main.go b/main.go
index 962ab33..c84f26c 100644
--- a/main.go
+++ b/main.go
@@ -30,6 +30,7 @@ var (
cleanup bool
noRestart bool
notifier *notifications.Notifier
+ timeout time.Duration
)
func init() {
@@ -81,6 +82,12 @@ func main() {
Usage: "use TLS and verify the remote",
EnvVar: "DOCKER_TLS_VERIFY",
},
+ cli.DurationFlag{
+ Name: "stop-timeout",
+ Usage: "timeout before container is forcefully stopped",
+ Value: time.Second * 10,
+ EnvVar: "WATCHTOWER_TIMEOUT",
+ },
cli.BoolFlag{
Name: "label-enable",
Usage: "watch containers where the com.centurylinklabs.watchtower.enable label is true",
@@ -178,6 +185,10 @@ func before(c *cli.Context) error {
cleanup = c.GlobalBool("cleanup")
noRestart = c.GlobalBool("no-restart")
+ timeout = c.GlobalDuration("stop-timeout")
+ if timeout < 0 {
+ log.Fatal("Please specify a positive value for timeout value.")
+ }
// configure environment vars for client
err := envConfig(c)
@@ -209,7 +220,7 @@ func start(c *cli.Context) error {
case v := <-tryLockSem:
defer func() { tryLockSem <- v }()
notifier.StartNotification()
- if err := actions.Update(client, names, cleanup, noRestart); err != nil {
+ if err := actions.Update(client, names, cleanup, noRestart, timeout); err != nil {
log.Println(err)
}
notifier.SendNotification()
From 026a04b59b2a09a9287cec57af50e77ce2f8f2c6 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 17:22:42 +0100
Subject: [PATCH 052/761] implemented enableLabel by a Filter
---
README.md | 6 ++---
actions/check.go | 4 +--
actions/update.go | 21 ++-------------
container/client.go | 25 +++++-------------
container/filters.go | 61 ++++++++++++++++++++++++++++++++++++++++++++
main.go | 8 ++++--
6 files changed, 79 insertions(+), 46 deletions(-)
create mode 100644 container/filters.go
diff --git a/README.md b/README.md
index 8b3b260..4f84340 100644
--- a/README.md
+++ b/README.md
@@ -129,9 +129,9 @@ docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimag
## Selectively Watching Containers
-By default, watchtower will watch all containers.
-However, sometimes only some containers should be updated.
-If you need to selectively watch containers, pass the --label-enable flag on startup and set the *com.centurylinklabs.watchtower.enable* label with a value of true for the containers you want to watch.
+By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
+
+If you need to only include only some containers, pass the --label-enable flag on startup and set the *com.centurylinklabs.watchtower.enable* label with a value of true for the containers you want to watch.
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
diff --git a/actions/check.go b/actions/check.go
index 8fb5786..5354dd7 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -6,14 +6,12 @@ import (
"github.com/v2tec/watchtower/container"
)
-func watchtowerContainersFilter(c container.Container) bool { return c.IsWatchtower() }
-
// CheckPrereqs will ensure that there are not multiple instances of the
// watchtower running simultaneously. If multiple watchtower containers are
// detected, this function will stop and remove all but the most recently
// started container.
func CheckPrereqs(client container.Client, cleanup bool) error {
- containers, err := client.ListContainers(watchtowerContainersFilter)
+ containers, err := client.ListContainers(container.WatchtowerContainersFilter)
if err != nil {
return err
}
diff --git a/actions/update.go b/actions/update.go
index 3866d4b..4d488a5 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -12,31 +12,14 @@ var (
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
-func allContainersFilter(container.Container) bool { return true }
-
-func containerFilter(names []string) container.Filter {
- if len(names) == 0 {
- return allContainersFilter
- }
-
- return func(c container.Container) bool {
- for _, name := range names {
- if (name == c.Name()) || (name == c.Name()[1:]) {
- return true
- }
- }
- return false
- }
-}
-
// Update looks at the running Docker containers to see if any of the images
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
-func Update(client container.Client, names []string, cleanup bool, noRestart bool, timeout time.Duration) error {
+func Update(client container.Client, filter container.Filter, cleanup bool, noRestart bool, timeout time.Duration) error {
log.Debug("Checking containers for updated images")
- containers, err := client.ListContainers(containerFilter(names))
+ containers, err := client.ListContainers(filter)
if err != nil {
return err
}
diff --git a/container/client.go b/container/client.go
index 340c51b..5a75163 100644
--- a/container/client.go
+++ b/container/client.go
@@ -5,10 +5,10 @@ import (
"io/ioutil"
"time"
- log "github.com/sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client"
+ log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@@ -16,10 +16,6 @@ const (
defaultStopSignal = "SIGTERM"
)
-// A Filter is a prototype for a function that can be used to filter the
-// results from a call to the ListContainers() method on the Client.
-type Filter func(Container) bool
-
// A Client is the interface through which watchtower interacts with the
// Docker API.
type Client interface {
@@ -37,20 +33,19 @@ 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, enableLabel bool) Client {
+func NewClient(pullImages bool) Client {
cli, err := dockerclient.NewEnvClient()
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
}
- return dockerClient{api: cli, pullImages: pullImages, enableLabel: enableLabel}
+ return dockerClient{api: cli, pullImages: pullImages}
}
type dockerClient struct {
- api *dockerclient.Client
- pullImages bool
- enableLabel bool
+ api *dockerclient.Client
+ pullImages bool
}
func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
@@ -62,6 +57,7 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
runningContainers, err := client.api.ContainerList(
bg,
types.ContainerListOptions{})
+
if err != nil {
return nil, err
}
@@ -79,15 +75,6 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
c := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
- if client.enableLabel {
- // If label filtering is enabled, containers should only be enabled
- // if the label is specifically set to true.
- enabledLabel, ok := c.Enabled()
- if !ok || !enabledLabel {
- continue
- }
- }
-
if fn(c) {
cs = append(cs, c)
}
diff --git a/container/filters.go b/container/filters.go
new file mode 100644
index 0000000..894cbc3
--- /dev/null
+++ b/container/filters.go
@@ -0,0 +1,61 @@
+package container
+
+// A Filter is a prototype for a function that can be used to filter the
+// results from a call to the ListContainers() method on the Client.
+type Filter func(FilterableContainer) bool
+
+// A FilterableContainer is the interface which is used to filter
+// containers.
+type FilterableContainer interface {
+ Name() string
+ IsWatchtower() bool
+ Enabled() (bool, bool)
+}
+
+// WatchtowerContainersFilter filters only watchtower containers
+func WatchtowerContainersFilter(c FilterableContainer) bool { return c.IsWatchtower() }
+
+// Filter no containers and returns all
+func noFilter(FilterableContainer) bool { return true }
+
+// Filters containers which don't have a specified name
+func filterByNames(names []string, baseFilter Filter) Filter {
+ if len(names) == 0 {
+ return baseFilter
+ }
+
+ return func(c FilterableContainer) bool {
+ for _, name := range names {
+ if (name == c.Name()) || (name == c.Name()[1:]) {
+ return baseFilter(c)
+ }
+ }
+ return false
+ }
+}
+
+// Filters out containers that don't have the 'enableLabel'
+func filterByEnableLabel(baseFilter Filter) Filter {
+ return func(c FilterableContainer) bool {
+ // If label filtering is enabled, containers should only be enabled
+ // if the label is specifically set to true.
+ enabledLabel, ok := c.Enabled()
+ if !ok || !enabledLabel {
+ return false
+ }
+
+ return baseFilter(c)
+ }
+}
+
+// BuildFilter creates the needed filter of containers
+func BuildFilter(names []string, enableLabel bool) Filter {
+ filter := noFilter
+ filter = filterByNames(names, filter)
+ if enableLabel {
+ // If label filtering is enabled, containers should only be considered
+ // if the label is specifically set.
+ filter = filterByEnableLabel(filter)
+ }
+ return filter
+}
diff --git a/main.go b/main.go
index c84f26c..97fe15d 100644
--- a/main.go
+++ b/main.go
@@ -29,6 +29,7 @@ var (
scheduleSpec string
cleanup bool
noRestart bool
+ enableLabel bool
notifier *notifications.Notifier
timeout time.Duration
)
@@ -189,6 +190,7 @@ func before(c *cli.Context) error {
if timeout < 0 {
log.Fatal("Please specify a positive value for timeout value.")
}
+ enableLabel = c.GlobalBool("label-enable")
// configure environment vars for client
err := envConfig(c)
@@ -196,7 +198,7 @@ func before(c *cli.Context) error {
return err
}
- client = container.NewClient(!c.GlobalBool("no-pull"), c.GlobalBool("label-enable"))
+ client = container.NewClient(!c.GlobalBool("no-pull"))
notifier = notifications.NewNotifier(c)
return nil
@@ -209,6 +211,8 @@ func start(c *cli.Context) error {
log.Fatal(err)
}
+ filter := container.BuildFilter(names, enableLabel)
+
tryLockSem := make(chan bool, 1)
tryLockSem <- true
@@ -220,7 +224,7 @@ func start(c *cli.Context) error {
case v := <-tryLockSem:
defer func() { tryLockSem <- v }()
notifier.StartNotification()
- if err := actions.Update(client, names, cleanup, noRestart, timeout); err != nil {
+ if err := actions.Update(client, filter, cleanup, noRestart, timeout); err != nil {
log.Println(err)
}
notifier.SendNotification()
From 6c12aee9752a6f879d9d2a4c8905c8b340c81570 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 18:08:57 +0100
Subject: [PATCH 053/761] always exclude containers that have the
com.centurylinklabs.watchtower.enable set to false.
Fixes #169
---
README.md | 12 ++++++++++++
container/filters.go | 20 +++++++++++++++++---
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 4f84340..74748f8 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,18 @@ docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimag
By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
+If you need to exclude some containers, set the *com.centurylinklabs.watchtower.enable* label to `false`.
+
+```docker
+LABEL com.centurylinklabs.watchtower.enable="false"
+```
+
+Or, it can be specified as part of the `docker run` command line:
+
+```bash
+docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
+```
+
If you need to only include only some containers, pass the --label-enable flag on startup and set the *com.centurylinklabs.watchtower.enable* label with a value of true for the containers you want to watch.
```docker
diff --git a/container/filters.go b/container/filters.go
index 894cbc3..1b3fbbd 100644
--- a/container/filters.go
+++ b/container/filters.go
@@ -37,10 +37,23 @@ func filterByNames(names []string, baseFilter Filter) Filter {
// Filters out containers that don't have the 'enableLabel'
func filterByEnableLabel(baseFilter Filter) Filter {
return func(c FilterableContainer) bool {
- // If label filtering is enabled, containers should only be enabled
- // if the label is specifically set to true.
+ // If label filtering is enabled, containers should only be considered
+ // if the label is specifically set.
+ _, ok := c.Enabled()
+ if !ok {
+ return false
+ }
+
+ return baseFilter(c)
+ }
+}
+
+// Filters out containers that have a 'enableLabel' and is set to disable.
+func filterByDisabledLabel(baseFilter Filter) Filter {
+ return func(c FilterableContainer) bool {
enabledLabel, ok := c.Enabled()
- if !ok || !enabledLabel {
+ if ok && !enabledLabel {
+ // If the label has been set and it demands a disable
return false
}
@@ -57,5 +70,6 @@ func BuildFilter(names []string, enableLabel bool) Filter {
// if the label is specifically set.
filter = filterByEnableLabel(filter)
}
+ filter = filterByDisabledLabel(filter)
return filter
}
From 6d4e7cffaf917635c14ba1d2f8725a7283f4cc7a Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 21:26:43 +0100
Subject: [PATCH 054/761] add tests for filters
---
container/filters_test.go | 152 +++++++++++++++++++++++++
container/mocks/FilterableContainer.go | 58 ++++++++++
2 files changed, 210 insertions(+)
create mode 100644 container/filters_test.go
create mode 100644 container/mocks/FilterableContainer.go
diff --git a/container/filters_test.go b/container/filters_test.go
new file mode 100644
index 0000000..1129be3
--- /dev/null
+++ b/container/filters_test.go
@@ -0,0 +1,152 @@
+package container
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/v2tec/watchtower/container/mocks"
+)
+
+func TestWatchtowerContainersFilter(t *testing.T) {
+ container := new(mocks.FilterableContainer)
+
+ container.On("IsWatchtower").Return(true)
+
+ assert.True(t, WatchtowerContainersFilter(container))
+
+ container.AssertExpectations(t)
+}
+
+func TestNoFilter(t *testing.T) {
+ container := new(mocks.FilterableContainer)
+
+ assert.True(t, noFilter(container))
+
+ container.AssertExpectations(t)
+}
+
+func TestFilterByNames(t *testing.T) {
+ filter := filterByNames(make([]string, 0), nil)
+ assert.Nil(t, filter)
+
+ names := make([]string, 0)
+ names = append(names, "test")
+
+ filter = filterByNames(names, noFilter)
+ assert.NotNil(t, filter)
+
+ container := new(mocks.FilterableContainer)
+ container.On("Name").Return("test")
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("NoTest")
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+}
+
+func TestFilterByEnableLabel(t *testing.T) {
+ filter := filterByEnableLabel(noFilter)
+ assert.NotNil(t, filter)
+
+ container := new(mocks.FilterableContainer)
+ container.On("Enabled").Return(true, true)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, true)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, false)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+}
+
+func TestFilterByDisabledLabel(t *testing.T) {
+ filter := filterByDisabledLabel(noFilter)
+ assert.NotNil(t, filter)
+
+ container := new(mocks.FilterableContainer)
+ container.On("Enabled").Return(true, true)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, true)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, false)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+}
+
+func TestBuildFilter(t *testing.T) {
+ names := make([]string, 0)
+ names = append(names, "test")
+
+ filter := BuildFilter(names, false)
+
+ container := new(mocks.FilterableContainer)
+ container.On("Name").Return("Invalid")
+ container.On("Enabled").Return(false, false)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("test")
+ container.On("Enabled").Return(false, false)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("Invalid")
+ container.On("Enabled").Return(true, true)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("test")
+ container.On("Enabled").Return(true, true)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, true)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+}
+
+func TestBuildFilterEnableLabel(t *testing.T) {
+ names := make([]string, 0)
+ names = append(names, "test")
+
+ filter := BuildFilter(names, true)
+
+ container := new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, false)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("Invalid")
+ container.On("Enabled").Twice().Return(true, true)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Name").Return("test")
+ container.On("Enabled").Twice().Return(true, true)
+ assert.True(t, filter(container))
+ container.AssertExpectations(t)
+
+ container = new(mocks.FilterableContainer)
+ container.On("Enabled").Return(false, true)
+ assert.False(t, filter(container))
+ container.AssertExpectations(t)
+}
diff --git a/container/mocks/FilterableContainer.go b/container/mocks/FilterableContainer.go
new file mode 100644
index 0000000..15f9db5
--- /dev/null
+++ b/container/mocks/FilterableContainer.go
@@ -0,0 +1,58 @@
+// Code generated by mockery v1.0.0
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+
+// FilterableContainer is an autogenerated mock type for the FilterableContainer type
+type FilterableContainer struct {
+ mock.Mock
+}
+
+// Enabled provides a mock function with given fields:
+func (_m *FilterableContainer) Enabled() (bool, bool) {
+ ret := _m.Called()
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func() bool); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ var r1 bool
+ if rf, ok := ret.Get(1).(func() bool); ok {
+ r1 = rf()
+ } else {
+ r1 = ret.Get(1).(bool)
+ }
+
+ return r0, r1
+}
+
+// IsWatchtower provides a mock function with given fields:
+func (_m *FilterableContainer) IsWatchtower() bool {
+ ret := _m.Called()
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func() bool); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
+// Name provides a mock function with given fields:
+func (_m *FilterableContainer) Name() string {
+ ret := _m.Called()
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func() string); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ return r0
+}
From 1f10817b4bf669909f6d0ebfce10edf9c2c04491 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 22:26:39 +0100
Subject: [PATCH 055/761] Removed unused mock code.
---
container/mockclient/mock.go | 42 -------------------------------
container/mockclient/mock_test.go | 17 -------------
2 files changed, 59 deletions(-)
delete mode 100644 container/mockclient/mock.go
delete mode 100644 container/mockclient/mock_test.go
diff --git a/container/mockclient/mock.go b/container/mockclient/mock.go
deleted file mode 100644
index 62cd321..0000000
--- a/container/mockclient/mock.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package mockclient
-
-import (
- "time"
-
- "github.com/stretchr/testify/mock"
- "github.com/v2tec/watchtower/container"
-)
-
-type MockClient struct {
- mock.Mock
-}
-
-func (m *MockClient) ListContainers(cf container.Filter) ([]container.Container, error) {
- args := m.Called(cf)
- return args.Get(0).([]container.Container), args.Error(1)
-}
-
-func (m *MockClient) StopContainer(c container.Container, timeout time.Duration) error {
- args := m.Called(c, timeout)
- return args.Error(0)
-}
-
-func (m *MockClient) StartContainer(c container.Container) error {
- args := m.Called(c)
- return args.Error(0)
-}
-
-func (m *MockClient) RenameContainer(c container.Container, name string) error {
- args := m.Called(c, name)
- return args.Error(0)
-}
-
-func (m *MockClient) IsContainerStale(c container.Container) (bool, error) {
- args := m.Called(c)
- return args.Bool(0), args.Error(1)
-}
-
-func (m *MockClient) RemoveImage(c container.Container) error {
- args := m.Called(c)
- return args.Error(0)
-}
diff --git a/container/mockclient/mock_test.go b/container/mockclient/mock_test.go
deleted file mode 100644
index 234a218..0000000
--- a/container/mockclient/mock_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package mockclient
-
-import (
- "reflect"
- "testing"
-
- "github.com/v2tec/watchtower/container"
-)
-
-func TestMockInterface(t *testing.T) {
- iface := reflect.TypeOf((*container.Client)(nil)).Elem()
- mock := &MockClient{}
-
- if !reflect.TypeOf(mock).Implements(iface) {
- t.Fatalf("Mock does not implement the Client interface")
- }
-}
From d0ac9f14aba908af2da273ed2c6e34ea6521cf41 Mon Sep 17 00:00:00 2001
From: Fabrizio Steiner
Date: Fri, 2 Mar 2018 22:37:50 +0100
Subject: [PATCH 056/761] some linting
---
container/client.go | 3 +-
container/container.go | 2 +-
container/filters_test.go | 9 +++---
container/mocks/FilterableContainer.go | 1 -
container/trust.go | 43 ++++++++++----------------
5 files changed, 23 insertions(+), 35 deletions(-)
diff --git a/container/client.go b/container/client.go
index 5a75163..476e3ca 100644
--- a/container/client.go
+++ b/container/client.go
@@ -216,10 +216,9 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
if newImageInfo.ID != oldImageInfo.ID {
log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
return true, nil
- } else {
- log.Debugf("No new images found for %s", c.Name())
}
+ log.Debugf("No new images found for %s", c.Name())
return false, nil
}
diff --git a/container/container.go b/container/container.go
index e620d92..7453507 100644
--- a/container/container.go
+++ b/container/container.go
@@ -155,7 +155,7 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
config.Volumes = structMapSubtract(config.Volumes, imageConfig.Volumes)
// subtract ports exposed in image from container
- for k, _ := range config.ExposedPorts {
+ for k := range config.ExposedPorts {
if _, ok := imageConfig.ExposedPorts[k]; ok {
delete(config.ExposedPorts, k)
}
diff --git a/container/filters_test.go b/container/filters_test.go
index 1129be3..25a0118 100644
--- a/container/filters_test.go
+++ b/container/filters_test.go
@@ -26,10 +26,11 @@ func TestNoFilter(t *testing.T) {
}
func TestFilterByNames(t *testing.T) {
- filter := filterByNames(make([]string, 0), nil)
+ var names []string
+
+ filter := filterByNames(names, nil)
assert.Nil(t, filter)
- names := make([]string, 0)
names = append(names, "test")
filter = filterByNames(names, noFilter)
@@ -87,7 +88,7 @@ func TestFilterByDisabledLabel(t *testing.T) {
}
func TestBuildFilter(t *testing.T) {
- names := make([]string, 0)
+ var names []string
names = append(names, "test")
filter := BuildFilter(names, false)
@@ -123,7 +124,7 @@ func TestBuildFilter(t *testing.T) {
}
func TestBuildFilterEnableLabel(t *testing.T) {
- names := make([]string, 0)
+ var names []string
names = append(names, "test")
filter := BuildFilter(names, true)
diff --git a/container/mocks/FilterableContainer.go b/container/mocks/FilterableContainer.go
index 15f9db5..508bd7c 100644
--- a/container/mocks/FilterableContainer.go
+++ b/container/mocks/FilterableContainer.go
@@ -1,4 +1,3 @@
-// Code generated by mockery v1.0.0
package mocks
import mock "github.com/stretchr/testify/mock"
diff --git a/container/trust.go b/container/trust.go
index 90ba335..e388302 100644
--- a/container/trust.go
+++ b/container/trust.go
@@ -5,20 +5,18 @@ import (
"os"
"strings"
- log "github.com/sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/reference"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/cliconfig/configfile"
"github.com/docker/docker/cliconfig/credentials"
+ log "github.com/sirupsen/logrus"
)
-/**
- * Return an encoded auth config for the given registry
- * loaded from environment variables or docker config
- * as available in that order
- */
+// EncodedAuth returns an encoded auth config for the given registry
+// loaded from environment variables or docker config
+// as available in that order
func EncodedAuth(ref string) (string, error) {
auth, err := EncodedEnvAuth(ref)
if err != nil {
@@ -27,11 +25,9 @@ func EncodedAuth(ref string) (string, error) {
return auth, err
}
-/*
- * Return an encoded auth config for the given registry
- * loaded from environment variables
- * Returns an error if authentication environment variables have not been set
- */
+// EncodedEnvAuth returns an encoded auth config for the given registry
+// loaded from environment variables
+// Returns an error if authentication environment variables have not been set
func EncodedEnvAuth(ref string) (string, error) {
username := os.Getenv("REPO_USER")
password := os.Getenv("REPO_PASS")
@@ -42,17 +38,14 @@ func EncodedEnvAuth(ref string) (string, error) {
}
log.Debugf("Loaded auth credentials %s for %s", auth, ref)
return EncodeAuth(auth)
- } else {
- return "", errors.New("Registry auth environment variables (REPO_USER, REPO_PASS) not set")
}
+ return "", errors.New("Registry auth environment variables (REPO_USER, REPO_PASS) not set")
}
-/*
- * Return an encoded auth config for the given registry
- * loaded from the docker config
- * Returns an empty string if credentials cannot be found for the referenced server
- * The docker config must be mounted on the container
- */
+// EncodedConfigAuth returns an encoded auth config for the given registry
+// loaded from the docker config
+// Returns an empty string if credentials cannot be found for the referenced server
+// The docker config must be mounted on the container
func EncodedConfigAuth(ref string) (string, error) {
server, err := ParseServerAddress(ref)
configDir := os.Getenv("DOCKER_CONFIG")
@@ -92,18 +85,14 @@ func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
return credentials.NewFileStore(&configFile)
}
-/*
- * Base64 encode an AuthConfig struct for transmission over HTTP
- */
+// EncodeAuth Base64 encode an AuthConfig struct for transmission over HTTP
func EncodeAuth(auth types.AuthConfig) (string, error) {
return command.EncodeAuthToBase64(auth)
}
-/**
- * This function will be invoked if an AuthConfig is rejected
- * It could be used to return a new value for the "X-Registry-Auth" authentication header,
- * but there's no point trying again with the same value as used in AuthConfig
- */
+// DefaultAuthHandler will be invoked if an AuthConfig is rejected
+// It could be used to return a new value for the "X-Registry-Auth" authentication header,
+// but there's no point trying again with the same value as used in AuthConfig
func DefaultAuthHandler() (string, error) {
log.Debug("Authentication request was rejected. Trying again without authentication")
return "", nil
From aa1ce68e3a769a7ca744db5bfdc85170d80808bb Mon Sep 17 00:00:00 2001
From: Maxim
Date: Tue, 13 Mar 2018 08:39:54 +0200
Subject: [PATCH 057/761] Notifications via MSTeams (#174)
* Notifications via MSTeams
---
README.md | 20 ++++++
main.go | 12 +++-
notifications/msteams.go | 134 ++++++++++++++++++++++++++++++++++++++
notifications/notifier.go | 2 +
4 files changed, 167 insertions(+), 1 deletion(-)
create mode 100644 notifications/msteams.go
diff --git a/README.md b/README.md
index 74748f8..bd20624 100644
--- a/README.md
+++ b/README.md
@@ -254,3 +254,23 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
v2tec/watchtower
```
+
+### Notifications via MSTeams incoming webhook
+
+To receive notifications in MSTeams channel, add `msteams` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
+
+Additionally, you should set the MSTeams webhook url using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
+
+MSTeams notifier could send keys/values filled by ```log.WithField``` or ```log.WithFields``` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=msteams \
+ -e WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL="https://outlook.office.com/webhook/xxxxxxxx@xxxxxxx/IncomingWebhook/yyyyyyyy/zzzzzzzzzz" \
+ -e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
+ v2tec/watchtower
+```
diff --git a/main.go b/main.go
index 97fe15d..2b6fba5 100644
--- a/main.go
+++ b/main.go
@@ -101,7 +101,7 @@ func main() {
cli.StringSliceFlag{
Name: "notifications",
Value: &cli.StringSlice{},
- Usage: "notification types to send (valid: email, slack)",
+ Usage: "notification types to send (valid: email, slack, msteams)",
EnvVar: "WATCHTOWER_NOTIFICATIONS",
},
cli.StringFlag{
@@ -161,6 +161,16 @@ func main() {
EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER",
Value: "watchtower",
},
+ cli.StringFlag{
+ Name: "notification-msteams-hook",
+ Usage: "The MSTeams WebHook URL to send notifications to",
+ EnvVar: "WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL",
+ },
+ cli.BoolFlag{
+ Name: "notification-msteams-data",
+ Usage: "The MSTeams notifier will try to extract log entry fields as MSTeams message facts",
+ EnvVar: "WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA",
+ },
}
if err := app.Run(os.Args); err != nil {
diff --git a/notifications/msteams.go b/notifications/msteams.go
new file mode 100644
index 0000000..8bb9d7a
--- /dev/null
+++ b/notifications/msteams.go
@@ -0,0 +1,134 @@
+package notifications
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+ "io/ioutil"
+)
+
+const (
+ msTeamsType = "msteams"
+)
+
+type msTeamsTypeNotifier struct {
+ webHookURL string
+ levels []log.Level
+ data bool
+}
+
+func newMsTeamsNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
+
+ webHookURL := c.GlobalString("notification-msteams-hook")
+ if len(webHookURL) <= 0 {
+ log.Fatal("Required argument --notification-msteams-hook(cli) or WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL(env) is empty.")
+ }
+
+ n := &msTeamsTypeNotifier{
+ levels: acceptedLogLevels,
+ webHookURL: webHookURL,
+ data: c.GlobalBool("notification-msteams-data"),
+ }
+
+ log.AddHook(n)
+
+ return n
+}
+
+func (n *msTeamsTypeNotifier) StartNotification() {}
+
+func (n *msTeamsTypeNotifier) SendNotification() {}
+
+func (n *msTeamsTypeNotifier) Levels() []log.Level {
+ return n.levels
+}
+
+func (n *msTeamsTypeNotifier) Fire(entry *log.Entry) error {
+
+ message := "(" + entry.Level.String() + "): " + entry.Message
+
+ go func() {
+ webHookBody := messageCard{
+ CardType: "MessageCard",
+ Context: "http://schema.org/extensions",
+ Markdown: true,
+ Text: message,
+ }
+
+ if n.data && entry.Data != nil && len(entry.Data) > 0 {
+ section := messageCardSection{
+ Facts: make([]messageCardSectionFact, len(entry.Data)),
+ Text: "",
+ }
+
+ index := 0
+ for k, v := range entry.Data {
+ section.Facts[index] = messageCardSectionFact{
+ Name: k,
+ Value: fmt.Sprint(v),
+ }
+ index++
+ }
+
+ webHookBody.Sections = []messageCardSection{section}
+ }
+
+ jsonBody, err := json.Marshal(webHookBody)
+ if err != nil {
+ fmt.Println("Failed to build JSON body for MSTeams notificattion: ", err)
+ return
+ }
+
+ resp, err := http.Post(n.webHookURL, "application/json", bytes.NewBuffer([]byte(jsonBody)))
+ if err != nil {
+ fmt.Println("Failed to send MSTeams notificattion: ", err)
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode > 299 {
+ fmt.Println("Failed to send MSTeams notificattion. HTTP RESPONSE STATUS: ", resp.StatusCode)
+ if resp.Body != nil {
+ bodyBytes, err := ioutil.ReadAll(resp.Body)
+ if err == nil {
+ bodyString := string(bodyBytes)
+ fmt.Println(bodyString)
+ }
+ }
+ }
+ }()
+
+ return nil
+}
+
+type messageCard struct {
+ CardType string `json:"@type"`
+ Context string `json:"@context"`
+ CorrelationID string `json:"correlationId,omitempty"`
+ ThemeColor string `json:"themeColor,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Title string `json:"title,omitempty"`
+ Text string `json:"text,omitempty"`
+ Markdown bool `json:"markdown,bool"`
+ Sections []messageCardSection `json:"sections,omitempty"`
+}
+
+type messageCardSection struct {
+ Title string `json:"title,omitempty"`
+ Text string `json:"text,omitempty"`
+ ActivityTitle string `json:"activityTitle,omitempty"`
+ ActivitySubtitle string `json:"activitySubtitle,omitempty"`
+ ActivityImage string `json:"activityImage,omitempty"`
+ ActivityText string `json:"activityText,omitempty"`
+ HeroImage string `json:"heroImage,omitempty"`
+ Facts []messageCardSectionFact `json:"facts,omitempty"`
+}
+
+type messageCardSectionFact struct {
+ Name string `json:"name,omitempty"`
+ Value string `json:"value,omitempty"`
+}
diff --git a/notifications/notifier.go b/notifications/notifier.go
index 274811b..62e8ebc 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -36,6 +36,8 @@ func NewNotifier(c *cli.Context) *Notifier {
tn = newEmailNotifier(c, acceptedLogLevels)
case slackType:
tn = newSlackNotifier(c, acceptedLogLevels)
+ case msTeamsType:
+ tn = newMsTeamsNotifier(c, acceptedLogLevels)
default:
log.Fatalf("Unknown notification type %q", t)
}
From 27795c24e1869645741a2f08a130176206a8f239 Mon Sep 17 00:00:00 2001
From: stffabi
Date: Tue, 13 Mar 2018 07:41:42 +0100
Subject: [PATCH 058/761] Add msteams to notifications overview
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index bd20624..810ee65 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,7 @@ The types of notifications to send are passed via the comma-separated option `--
* `email` to send notifications via e-mail
* `slack` to send notifications through a Slack webhook
+* `msteams` to send notifications via MSTeams webhook
### Settings
From 21bcca3326b11e117fd3077b95cc9cdbbb5fd127 Mon Sep 17 00:00:00 2001
From: Napster
Date: Mon, 19 Mar 2018 14:00:30 +0100
Subject: [PATCH 059/761] Add arm64v8 build
---
dockerfile/arm64v8/Dockerfile | 20 ++++++++++++++++++++
dockerfile/push_containers.sh | 3 ++-
goreleaser.yml | 13 ++++++++++++-
3 files changed, 34 insertions(+), 2 deletions(-)
create mode 100644 dockerfile/arm64v8/Dockerfile
diff --git a/dockerfile/arm64v8/Dockerfile b/dockerfile/arm64v8/Dockerfile
new file mode 100644
index 0000000..13b19e8
--- /dev/null
+++ b/dockerfile/arm64v8/Dockerfile
@@ -0,0 +1,20 @@
+#
+# Alpine image to get some needed data
+#
+FROM alpine:latest as alpine
+RUN apk add --no-cache \
+ ca-certificates \
+ tzdata
+
+#
+# Image
+#
+FROM scratch
+LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other containers
+COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
+
+COPY watchtower /
+ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/push_containers.sh b/dockerfile/push_containers.sh
index fe082bb..0ee059f 100644
--- a/dockerfile/push_containers.sh
+++ b/dockerfile/push_containers.sh
@@ -49,4 +49,5 @@ push_all() {
IMAGE_NAME=v2tec/watchtower
push_all ${IMAGE_NAME}:
-push_all ${IMAGE_NAME}:armhf-
\ No newline at end of file
+push_all ${IMAGE_NAME}:armhf-
+push_all ${IMAGE_NAME}:arm64v8-
diff --git a/goreleaser.yml b/goreleaser.yml
index 0d457ec..fed2baa 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -17,6 +17,7 @@ build:
goarch:
- amd64
- arm
+ - arm64
# Archive customization
archive:
@@ -35,6 +36,7 @@ archive:
# of `uname -s` and `uname -m` respectively.
replacements:
arm: armhf
+ arm64: arm64v8
amd64: amd64
386: 386
darwin: macOS
@@ -68,4 +70,13 @@ dockers:
image: v2tec/watchtower
dockerfile: dockerfile/armhf/Dockerfile
tag_templates:
- - 'armhf-{{ .Version }}'
\ No newline at end of file
+ - 'armhf-{{ .Version }}'
+ -
+ goos: linux
+ goarch: arm64
+ goarm: ''
+ binary: watchtower
+ image: v2tec/watchtower
+ dockerfile: dockerfile/arm64v8/Dockerfile
+ tag_templates:
+ - 'arm64v8-{{ .Version }}'
From bb2ab925cf30668a2ec22fee141a53b843b0c107 Mon Sep 17 00:00:00 2001
From: Jason Huddleston
Date: Sat, 24 Mar 2018 12:05:12 -0400
Subject: [PATCH 060/761] Spelling updates for README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 810ee65..5bc2100 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ CONTAINER ID IMAGE STATUS PORTS
6cc4d2a9d1a5 v2tec/watchtower Up 15 minutes watchtower
```
-Every few mintutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
+Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
## Usage
From 5d16df9f6da517e25bf82bc8e64608535d442da2 Mon Sep 17 00:00:00 2001
From: Connor McCormick <22061955+cnrmck@users.noreply.github.com>
Date: Mon, 16 Jul 2018 17:53:44 +0000
Subject: [PATCH 061/761] Fix confusing word
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 5bc2100..84d7062 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ docker run -d \
v2tec/watchtower container_to_watch --debug
```
-If you mount the config file as described below, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
+If you mount the config file as described above, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
```json
version: "3"
@@ -93,7 +93,7 @@ docker run --rm v2tec/watchtower --help
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
-* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *" `
+* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *" `
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
* `--stop-timeout` Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
* `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
From b72a5bf9e76112a53072359f178eb9f14ec68b2d Mon Sep 17 00:00:00 2001
From: Connor McCormick <22061955+cnrmck@users.noreply.github.com>
Date: Mon, 16 Jul 2018 17:55:57 +0000
Subject: [PATCH 062/761] Add documentation for how to pass env variables
---
README.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/README.md b/README.md
index 84d7062..895e59e 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,18 @@ docker run -d \
If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
+Passing environment variables:
+```bash
+docker run -d \
+ --name watchtower \
+ -e REPO_USER=username \
+ -e REPO_PASS=password \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ v2tec/watchtower container_to_watch --debug
+```
+Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
+
+Mounting the host's docker config file:
```bash
docker run -d \
--name watchtower \
From 9ffe960274b58ce91953ba58df2076a1ea9435a6 Mon Sep 17 00:00:00 2001
From: Connor McCormick <22061955+cnrmck@users.noreply.github.com>
Date: Mon, 16 Jul 2018 18:00:52 +0000
Subject: [PATCH 063/761] Add reminder to pull proper image for ARM users
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 895e59e..0ee0cc8 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image
## Usage
-Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `v2tec/watchtower` image.
+Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `v2tec/watchtower` image. If you are using ARM based architecture, pull the appropriate `v2tec/watchtower:armhf-` image from the [v2tec Docker Hub](https://hub.docker.com/r/v2tec/watchtower/tags/).
Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount */var/run/docker.sock* into the container with the -v flag when you run it.
From df77d482fb5c6a7940fb57f28af5538003a700bd Mon Sep 17 00:00:00 2001
From: Scott Albertson
Date: Fri, 16 Nov 2018 21:06:18 -0800
Subject: [PATCH 064/761] Add a "Reviewed by Hound" badge
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 5bc2100..ff15d3b 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
[](https://godoc.org/github.com/v2tec/watchtower)
[](https://microbadger.com/images/v2tec/watchtower "Get your own image badge on microbadger.com")
[](https://goreportcard.com/report/github.com/v2tec/watchtower)
+[](https://houndci.com)
A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.
From daeeee976198a422730f11780b526a353e5b60d2 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Apr 2019 21:00:24 +0200
Subject: [PATCH 065/761] switch refs in readme
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index ff15d3b..190c2b6 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# Watchtower
-[](https://circleci.com/gh/v2tec/watchtower)
-[](https://godoc.org/github.com/v2tec/watchtower)
-[](https://microbadger.com/images/v2tec/watchtower "Get your own image badge on microbadger.com")
-[](https://goreportcard.com/report/github.com/v2tec/watchtower)
+[](https://circleci.com/gh/containrrr/watchtower)
+[](https://godoc.org/github.com/containrrr/watchtower)
+[](https://microbadger.com/images/containrrr/watchtower "Get your own image badge on microbadger.com")
+[](https://goreportcard.com/report/github.com/containrrr/watchtower)
[](https://houndci.com)
A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.
From aaaddc8902de745ffd4a18dce762b4402d587818 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 3 Apr 2019 18:16:54 +0200
Subject: [PATCH 066/761] change push dir for image
---
dockerfile/push_containers.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dockerfile/push_containers.sh b/dockerfile/push_containers.sh
index 0ee059f..d231f87 100644
--- a/dockerfile/push_containers.sh
+++ b/dockerfile/push_containers.sh
@@ -47,7 +47,7 @@ push_all() {
push_docker ${IMAGE_NAME_VERSION} ${1}latest
}
-IMAGE_NAME=v2tec/watchtower
+IMAGE_NAME=containrrr/watchtower
push_all ${IMAGE_NAME}:
push_all ${IMAGE_NAME}:armhf-
push_all ${IMAGE_NAME}:arm64v8-
From ab299b59f8e227647674421e4374d0bc4377a678 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 3 Apr 2019 21:02:05 +0200
Subject: [PATCH 067/761] change repo paths
---
README.md | 30 +++++++++++++++---------------
actions/check.go | 2 +-
actions/update.go | 2 +-
container/filters_test.go | 2 +-
glide.yaml | 2 +-
goreleaser.yml | 6 +++---
main.go | 8 ++++----
7 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 268c611..89ce03f 100644
--- a/README.md
+++ b/README.md
@@ -20,14 +20,14 @@ For example, let's say you were running watchtower along with an instance of *ce
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
-6cc4d2a9d1a5 v2tec/watchtower Up 15 minutes watchtower
+6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
```
Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
## Usage
-Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `v2tec/watchtower` image. If you are using ARM based architecture, pull the appropriate `v2tec/watchtower:armhf-` image from the [v2tec Docker Hub](https://hub.docker.com/r/v2tec/watchtower/tags/).
+Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [v2tec Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount */var/run/docker.sock* into the container with the -v flag when you run it.
@@ -37,7 +37,7 @@ Run the `watchtower` container with the following command:
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
- v2tec/watchtower
+ containrrr/watchtower
```
If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
@@ -50,7 +50,7 @@ docker run -d \
-e REPO_USER=username \
-e REPO_PASS=password \
-v /var/run/docker.sock:/var/run/docker.sock \
- v2tec/watchtower container_to_watch --debug
+ containrrr/watchtower container_to_watch --debug
```
Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
@@ -60,7 +60,7 @@ docker run -d \
--name watchtower \
-v /home//.docker/config.json:/config.json \
-v /var/run/docker.sock:/var/run/docker.sock \
- v2tec/watchtower container_to_watch --debug
+ containrrr/watchtower container_to_watch --debug
```
If you mount the config file as described above, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
@@ -74,7 +74,7 @@ services:
- "443:3443"
- "80:3080"
watchtower:
- image: v2tec/watchtower
+ image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /root/.docker/config.json:/config.json
@@ -89,7 +89,7 @@ By default, watchtower will monitor all containers running within the Docker dae
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
- v2tec/watchtower nginx redis
+ containrrr/watchtower nginx redis
```
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other running containers will be ignored.
@@ -101,7 +101,7 @@ When no arguments are specified, watchtower will monitor all running containers.
Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string:
```bash
-docker run --rm v2tec/watchtower --help
+docker run --rm containrrr/watchtower --help
```
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
@@ -175,7 +175,7 @@ By default, watchtower is set-up to monitor the local Docker daemon (the same da
```bash
docker run -d \
--name watchtower \
- v2tec/watchtower --host "tcp://10.0.1.2:2375"
+ containrrr/watchtower --host "tcp://10.0.1.2:2375"
```
or
@@ -184,7 +184,7 @@ or
docker run -d \
--name watchtower \
-e DOCKER_HOST="tcp://10.0.1.2:2375" \
- v2tec/watchtower
+ containrrr/watchtower
```
Note in both of the examples above that it is unnecessary to mount the */var/run/docker.sock* into the watchtower container.
@@ -202,12 +202,12 @@ docker run -d \
--name watchtower \
-e DOCKER_HOST=$DOCKER_HOST \
-v $DOCKER_CERT_PATH:/etc/ssl/docker \
- v2tec/watchtower --tlsverify
+ containrrr/watchtower --tlsverify
```
## Updating Watchtower
-If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *v2tec/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
+If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *containrrr/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
## Notifications
@@ -246,7 +246,7 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
- v2tec/watchtower
+ containrrr/watchtower
```
### Notifications through Slack webhook
@@ -266,7 +266,7 @@ docker run -d \
-e WATCHTOWER_NOTIFICATIONS=slack \
-e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
-e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
- v2tec/watchtower
+ containrrr/watchtower
```
### Notifications via MSTeams incoming webhook
@@ -286,5 +286,5 @@ docker run -d \
-e WATCHTOWER_NOTIFICATIONS=msteams \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL="https://outlook.office.com/webhook/xxxxxxxx@xxxxxxx/IncomingWebhook/yyyyyyyy/zzzzzzzzzz" \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
- v2tec/watchtower
+ containrrr/watchtower
```
diff --git a/actions/check.go b/actions/check.go
index 5354dd7..2517dc2 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -3,7 +3,7 @@ package actions
import (
"sort"
- "github.com/v2tec/watchtower/container"
+ "github.com/containrrr/watchtower/container"
)
// CheckPrereqs will ensure that there are not multiple instances of the
diff --git a/actions/update.go b/actions/update.go
index 4d488a5..4f1e307 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -5,7 +5,7 @@ import (
"time"
log "github.com/sirupsen/logrus"
- "github.com/v2tec/watchtower/container"
+ "github.com/containrrr/watchtower/container"
)
var (
diff --git a/container/filters_test.go b/container/filters_test.go
index 25a0118..0db0a62 100644
--- a/container/filters_test.go
+++ b/container/filters_test.go
@@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/v2tec/watchtower/container/mocks"
+ "github.com/containrrr/watchtower/container/mocks"
)
func TestWatchtowerContainersFilter(t *testing.T) {
diff --git a/glide.yaml b/glide.yaml
index 20ef77d..bd9ed6d 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,4 +1,4 @@
-package: github.com/v2tec/watchtower
+package: github.com/containrrr/watchtower
import:
- package: github.com/sirupsen/logrus
version: ~0.11.x
diff --git a/goreleaser.yml b/goreleaser.yml
index fed2baa..c0ea4c3 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -58,7 +58,7 @@ dockers:
goarch: amd64
goarm: ''
binary: watchtower
- image: v2tec/watchtower
+ image: containrrr/watchtower
dockerfile: dockerfile/amd64/Dockerfile
tag_templates:
- '{{ .Version }}'
@@ -67,7 +67,7 @@ dockers:
goarch: arm
goarm: 6
binary: watchtower
- image: v2tec/watchtower
+ image: containrrr/watchtower
dockerfile: dockerfile/armhf/Dockerfile
tag_templates:
- 'armhf-{{ .Version }}'
@@ -76,7 +76,7 @@ dockers:
goarch: arm64
goarm: ''
binary: watchtower
- image: v2tec/watchtower
+ image: containrrr/watchtower
dockerfile: dockerfile/arm64v8/Dockerfile
tag_templates:
- 'arm64v8-{{ .Version }}'
diff --git a/main.go b/main.go
index 2b6fba5..c686b65 100644
--- a/main.go
+++ b/main.go
@@ -1,4 +1,4 @@
-package main // import "github.com/v2tec/watchtower"
+package main // import "github.com/containrrr/watchtower"
import (
"os"
@@ -11,9 +11,9 @@ import (
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
- "github.com/v2tec/watchtower/actions"
- "github.com/v2tec/watchtower/container"
- "github.com/v2tec/watchtower/notifications"
+ "github.com/containrrr/watchtower/actions"
+ "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/notifications"
)
// DockerAPIMinVersion is the version of the docker API, which is minimally required by
From c03b6c4235c823a31d9e54f3d5637a961f3f6614 Mon Sep 17 00:00:00 2001
From: Jan Wagner
Date: Thu, 4 Apr 2019 16:17:50 +0200
Subject: [PATCH 068/761] Fixing name of Docker Hub account
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 89ce03f..c0dfbf2 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image
## Usage
-Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [v2tec Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
+Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount */var/run/docker.sock* into the container with the -v flag when you run it.
From 1b82da1ab77bf47e2243da15403a58a300c4324f Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Thu, 4 Apr 2019 23:08:44 +0200
Subject: [PATCH 069/761] Setup a working pipeline
* add tests to ensure function even after switching docker client api version
* switch docker client api version to remove import of Sirupsen and get rid of the casing workaround
* migrate from glide to dep to go modules
* rewrite ci workflow
* only run publish on version tags
* only run build on branches
* update goreleaser config
* disable automated latest tag push
* remove dependency to v2tec/docker-gobuilder
* remove dead code and files
* add golands .idea folder to gitignore
* add label to released docker images
* add test reporting, add some unit tests
* change test output dir
* fix goreleaser versions
* add debug output for circleci and goreleaser
* disable cgo
---
.circleci/config.yml | 116 ++++++++++++++++++++
.gitignore | 3 +-
actions/update.go | 5 +-
circle.yml | 28 -----
container/client.go | 2 +
container/container.go | 14 ++-
container/container_test.go | 25 +++++
container/trust.go | 17 +--
container/trust_test.go | 60 +++++++++++
dockerfile/push_containers.sh | 53 ----------
glide.lock | 193 ----------------------------------
glide.yaml | 33 ------
go.mod | 53 ++++++++++
go.sum | 148 ++++++++++++++++++++++++++
goreleaser.yml | 61 ++++-------
notifications/smtp.go | 1 +
16 files changed, 450 insertions(+), 362 deletions(-)
create mode 100644 .circleci/config.yml
delete mode 100644 circle.yml
create mode 100644 container/container_test.go
create mode 100644 container/trust_test.go
delete mode 100644 dockerfile/push_containers.sh
delete mode 100644 glide.lock
delete mode 100644 glide.yaml
create mode 100644 go.mod
create mode 100644 go.sum
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..82031be
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,116 @@
+version: 2.1
+
+executors:
+ go:
+ docker:
+ - image: circleci/golang:latest
+ working_directory: ~/repo
+
+workflows:
+ version: 2
+ ci:
+ jobs:
+ - checkout:
+ filters:
+ branches:
+ only: /.*/
+ tags:
+ only: /.*/
+ - linting:
+ requires:
+ - checkout
+ filters:
+ branches:
+ only: /.*/
+ tags:
+ only: /.*/
+ - testing:
+ requires:
+ - checkout
+ filters:
+ branches:
+ only: /.*/
+ tags:
+ only: /.*/
+ - build:
+ requires:
+ - testing
+ - linting
+ filters:
+ tags:
+ ignore: /^v[0-9]+(\.[0-9]+)*$/
+ branches:
+ only: /.*/
+ - publishing:
+ requires:
+ - testing
+ - linting
+ filters:
+ branches:
+ ignore: /.*/
+ tags:
+ only: /^v[0-9]+(\.[0-9]+)*$/
+jobs:
+ checkout:
+ executor: go
+ steps:
+ - checkout
+ - persist_to_workspace:
+ paths:
+ - .
+ root: ~/repo
+ linting:
+ executor: go
+ steps:
+ - attach_workspace:
+ at: .
+ - run: go build .
+ - run: go get -u golang.org/x/lint/golint
+ - run: golint -set_exit_status ./...
+ testing:
+ executor: go
+ steps:
+ - attach_workspace:
+ at: .
+ - run: mkdir -p /tmp/test-results
+ - run: go build ./...
+ - run: go get gotest.tools/gotestsum
+ - run: gotestsum --junitfile /tmp/test-results/unit-tests.xml
+ - store_test_results:
+ path: /tmp/test-results
+ build:
+ executor: go
+ steps:
+ - attach_workspace:
+ at: .
+ - setup_remote_docker
+ - run:
+ name: Install Goreleaser
+ command: |
+ cd .. && \
+ wget https://github.com/goreleaser/goreleaser/releases/download/v0.104.1/goreleaser_Linux_x86_64.tar.gz && \
+ tar -xvf goreleaser_Linux_x86_64.tar.gz && \
+ ./goreleaser -v
+ - run:
+ name: Execute goreleaser
+ command: ../goreleaser --snapshot --skip-publish --debug
+ publishing:
+ executor: go
+ steps:
+ - attach_workspace:
+ at: .
+ - setup_remote_docker
+ - run:
+ name: Install Goreleaser
+ command: |
+ cd .. && \
+ wget https://github.com/goreleaser/goreleaser/releases/download/v0.104.1/goreleaser_Linux_x86_64.tar.gz && \
+ tar -xvf goreleaser_Linux_x86_64.tar.gz && \
+ ./goreleaser -v
+ - run:
+ name: Login to docker hub
+ command: |
+ echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin
+ - run:
+ name: Execute goreleaser
+ command: CGO_ENABLED=${CGO_ENABLED:-0} ../goreleaser --debug
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e43f117..79e0afc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
watchtower
vendor
.glide
-dist
\ No newline at end of file
+dist
+.idea
\ No newline at end of file
diff --git a/actions/update.go b/actions/update.go
index 4f1e307..e631b1a 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -4,12 +4,12 @@ import (
"math/rand"
"time"
- log "github.com/sirupsen/logrus"
"github.com/containrrr/watchtower/container"
+ log "github.com/sirupsen/logrus"
)
var (
- letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
// Update looks at the running Docker containers to see if any of the images
@@ -45,6 +45,7 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
container := containers[i]
if container.IsWatchtower() {
+ log.Debugf("This is the watchtower container %s", containers[i].Name())
continue
}
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index 6cbcc39..0000000
--- a/circle.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-version: 2
-jobs:
- build:
- docker:
- - image: v2tec/gobuilder:0.5.0_go1.7.4-glide0.12.3-goreleaser0.59.0-docker17.05.0
- working_directory: /src
- steps:
- - checkout
- - setup_remote_docker:
- version: 17.05.0-ce
- - run: git fetch --tags
- - run: |
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- IS_RELEASE=$(if [ "$CIRCLE_TAG" != "" ] ; then echo release; else echo ci; fi;)
- /build.sh $IS_RELEASE || exit 1
- chmod 755 /src/dockerfile/push_containers.sh
- if [ "$CIRCLE_TAG" != "" ] ; then /src/dockerfile/push_containers.sh $CIRCLE_TAG; fi;
-
- - store_artifacts:
- path: /src/dist/
-workflows:
- version: 2
- build-deploy:
- jobs:
- - build:
- filters:
- tags:
- only: /.*/
\ No newline at end of file
diff --git a/container/client.go b/container/client.go
index 476e3ca..47e4776 100644
--- a/container/client.go
+++ b/container/client.go
@@ -187,6 +187,8 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
auth, err := EncodedAuth(imageName)
+ log.Infof("Got auth value: %s", auth)
+ log.Infof("Got image name: %s", imageName)
if err != nil {
log.Debugf("Error loading authentication credentials %s", err)
return false, err
diff --git a/container/container.go b/container/container.go
index 7453507..7c4d948 100644
--- a/container/container.go
+++ b/container/container.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strconv"
"strings"
-
+ log "github.com/sirupsen/logrus"
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
)
@@ -102,8 +102,9 @@ func (c Container) Links() []string {
// identified by the presence of the "com.centurylinklabs.watchtower" label in
// the container metadata.
func (c Container) IsWatchtower() bool {
- val, ok := c.containerInfo.Config.Labels[watchtowerLabel]
- return ok && val == "true"
+ log.Debugf("Checking if %s is a watchtower instance.", c.Name())
+ wasWatchtower := ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
+ return wasWatchtower
}
// StopSignal returns the custom stop signal (if any) that is encoded in the
@@ -182,3 +183,10 @@ func (c Container) hostConfig() *dockercontainer.HostConfig {
return hostConfig
}
+
+// ContainsWatchtowerLabel takes a map of labels and values and tells
+// the consumer whether it contains a valid watchtower instance label
+func ContainsWatchtowerLabel(labels map[string]string) bool {
+ val, ok := labels[watchtowerLabel]
+ return ok && val == "true"
+}
\ No newline at end of file
diff --git a/container/container_test.go b/container/container_test.go
new file mode 100644
index 0000000..e3a7f94
--- /dev/null
+++ b/container/container_test.go
@@ -0,0 +1,25 @@
+package container
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestContainsWatchtowerLabel_ShouldReturnTrueIfTheWatchtowerLabelExistsOnTheContainer(t *testing.T) {
+ labelMap := map[string]string {
+ "com.centurylinklabs.watchtower": "true",
+ }
+ assert.True(t, ContainsWatchtowerLabel(labelMap))
+}
+
+func TestContainsWatchtowerLabel_ShouldReturnFalseIfTheWatchtowerLabelDoesntExistOnTheContainer(t *testing.T) {
+ labelMap := map[string]string {
+ "com.containrrr.watchtower": "lost",
+ }
+ assert.False(t, ContainsWatchtowerLabel(labelMap))
+}
+
+func TestContainsWatchtowerLabel_ShouldReturnFalseIfLabelsIsEmpty(t *testing.T) {
+ labelMap := map[string]string {}
+ assert.False(t, ContainsWatchtowerLabel(labelMap))
+}
diff --git a/container/trust.go b/container/trust.go
index e388302..92ab696 100644
--- a/container/trust.go
+++ b/container/trust.go
@@ -5,12 +5,12 @@ import (
"os"
"strings"
+ "github.com/docker/cli/cli/command"
+ cliconfig "github.com/docker/cli/cli/config"
+ "github.com/docker/cli/cli/config/configfile"
+ "github.com/docker/cli/cli/config/credentials"
+ "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
- "github.com/docker/docker/api/types/reference"
- "github.com/docker/docker/cli/command"
- "github.com/docker/docker/cliconfig"
- "github.com/docker/docker/cliconfig/configfile"
- "github.com/docker/docker/cliconfig/credentials"
log "github.com/sirupsen/logrus"
)
@@ -67,12 +67,15 @@ func EncodedConfigAuth(ref string) (string, error) {
return EncodeAuth(auth)
}
+// ParseServerAddress extracts the server part from a container image ref
func ParseServerAddress(ref string) (string, error) {
- repository, _, err := reference.Parse(ref)
+
+ parsedRef, err := reference.Parse(ref)
if err != nil {
return ref, err
}
- parts := strings.Split(repository, "/")
+
+ parts := strings.Split(parsedRef.String(), "/")
return parts[0], nil
}
diff --git a/container/trust_test.go b/container/trust_test.go
new file mode 100644
index 0000000..481ce64
--- /dev/null
+++ b/container/trust_test.go
@@ -0,0 +1,60 @@
+package container
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEncodedEnvAuth_ShouldReturnAnErrorIfRepoEnvsAreUnset(t *testing.T) {
+ os.Unsetenv("REPO_USER")
+ os.Unsetenv("REPO_PASS")
+ _, err := EncodedEnvAuth("")
+ assert.Error(t, err)
+}
+func TestEncodedEnvAuth_ShouldReturnAuthHashIfRepoEnvsAreSet(t *testing.T) {
+ expectedHash := "eyJ1c2VybmFtZSI6ImNvbnRhaW5ycnItdXNlciIsInBhc3N3b3JkIjoiY29udGFpbnJyci1wYXNzIn0="
+
+ os.Setenv("REPO_USER", "containrrr-user")
+ os.Setenv("REPO_PASS", "containrrr-pass")
+ config, _ := EncodedEnvAuth("")
+
+ assert.Equal(t, config, expectedHash)
+}
+
+func TestEncodedConfigAuth_ShouldReturnAnErrorIfFileIsNotPresent(t *testing.T) {
+ os.Setenv("DOCKER_CONFIG", "/dev/null/should-fail")
+ _, err := EncodedConfigAuth("")
+ assert.Error(t, err)
+}
+
+/*
+ * TODO:
+ * This part only confirms that it still works in the same way as it did
+ * with the old version of the docker api client sdk. I'd say that
+ * ParseServerAddress likely needs to be elaborated a bit to default to
+ * dockerhub in case no server address was provided.
+ *
+ * ++ @simskij, 2019-04-04
+ */
+
+func TestParseServerAddress_ShouldReturnErrorIfPassedEmptyString(t *testing.T) {
+ _, err := ParseServerAddress("")
+ assert.Error(t, err)
+}
+
+func TestParseServerAddress_ShouldReturnTheRepoNameIfPassedAFullyQualifiedImageName(t *testing.T) {
+ val, _ := ParseServerAddress("github.com/containrrrr/config")
+ assert.Equal(t, val, "github.com")
+}
+
+func TestParseServerAddress_ShouldReturnTheOrganizationPartIfPassedAnImageNameMissingServerName(t *testing.T) {
+ val, _ := ParseServerAddress("containrrr/config")
+ assert.Equal(t, val, "containrrr")
+}
+
+func TestParseServerAddress_ShouldReturnTheServerNameIfPassedAFullyQualifiedImageName(t *testing.T) {
+ val, _ := ParseServerAddress("github.com/containrrrr/config")
+ assert.Equal(t, val, "github.com")
+}
diff --git a/dockerfile/push_containers.sh b/dockerfile/push_containers.sh
deleted file mode 100644
index d231f87..0000000
--- a/dockerfile/push_containers.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-
-PROGNAME=$(basename $0)
-VERSION_BUILD=$1
-
-function error_exit
-{
- echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
- exit 1
-}
-
-if [ "$1" = "" ]; then
- error_exit "Please provide version as first argument."
-fi
-
-SEMVER=${VERSION_BUILD#*v}
-VERSION=`echo $SEMVER | awk '{split($0,a,"."); print a[1]}'`
-BUILD=`echo $SEMVER | awk '{split($0,a,"."); print a[2]}'`
-PATCH=`echo $SEMVER | awk '{split($0,a,"."); print a[3]}'`
-
-if [ "${VERSION}" = "" ]; then
- echo "Please provide a semantic version."
- exit 1
-fi
-
-if [ "${BUILD}" = "" ]; then
- BUILD='0'
-fi
-
-if [ "${PATCH}" = "" ]; then
- PATCH='0'
-fi
-
-push_docker() {
- echo " -> push $1 $2"
- docker tag $1 $2 || exit 1
- docker push $2 || exit 1
-}
-
-push_all() {
- IMAGE_NAME_VERSION=${1}${VERSION}.${BUILD}.${PATCH}
- echo "Pulling $IMAGE_NAME_VERSION..."
- docker pull ${IMAGE_NAME_VERSION} || exit 1
- echo "Pushing $IMAGE_NAME_VERSION..."
- push_docker ${IMAGE_NAME_VERSION} ${1}${VERSION}.${BUILD}
- push_docker ${IMAGE_NAME_VERSION} ${1}${VERSION}
- push_docker ${IMAGE_NAME_VERSION} ${1}latest
-}
-
-IMAGE_NAME=containrrr/watchtower
-push_all ${IMAGE_NAME}:
-push_all ${IMAGE_NAME}:armhf-
-push_all ${IMAGE_NAME}:arm64v8-
diff --git a/glide.lock b/glide.lock
deleted file mode 100644
index a4dcf44..0000000
--- a/glide.lock
+++ /dev/null
@@ -1,193 +0,0 @@
-hash: 23bcd62f9352c61d3aebc85a59f7ebbfaf80bc0201f45037cdea65820673ddeb
-updated: 2018-03-01T13:21:55.8472118+01:00
-imports:
-- name: github.com/Azure/go-ansiterm
- version: 388960b655244e76e24c75f48631564eaefade62
- subpackages:
- - winterm
-- name: github.com/davecgh/go-spew
- version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
- subpackages:
- - spew
-- name: github.com/docker/distribution
- version: 28602af35aceda2f8d571bad7ca37a54cf0250bc
- subpackages:
- - context
- - digest
- - reference
- - registry/api/errcode
- - registry/api/v2
- - registry/client
- - registry/client/auth
- - registry/client/auth/challenge
- - registry/client/transport
- - registry/storage/cache
- - registry/storage/cache/memory
- - uuid
-- name: github.com/docker/docker
- version: 092cba3727bb9b4a2f0e922cd6c0f93ea270e363
- subpackages:
- - api
- - api/server/httputils
- - api/types
- - api/types/blkiodev
- - api/types/container
- - api/types/events
- - api/types/filters
- - api/types/mount
- - api/types/network
- - api/types/reference
- - api/types/registry
- - api/types/strslice
- - api/types/swarm
- - api/types/time
- - api/types/versions
- - api/types/volume
- - cli/command
- - cli/flags
- - cliconfig
- - cliconfig/configfile
- - cliconfig/credentials
- - client
- - daemon/graphdriver
- - dockerversion
- - image
- - image/v1
- - layer
- - oci
- - opts
- - pkg/archive
- - pkg/chrootarchive
- - pkg/fileutils
- - pkg/homedir
- - pkg/httputils
- - pkg/idtools
- - pkg/ioutils
- - pkg/jsonlog
- - pkg/jsonmessage
- - pkg/longpath
- - pkg/mount
- - pkg/parsers/kernel
- - pkg/plugingetter
- - pkg/plugins
- - pkg/plugins/transport
- - pkg/pools
- - pkg/promise
- - pkg/random
- - pkg/reexec
- - pkg/stringid
- - pkg/system
- - pkg/tarsum
- - pkg/term
- - pkg/term/windows
- - pkg/tlsconfig
- - pkg/useragent
- - plugin/v2
- - reference
- - registry
-- name: github.com/docker/docker-credential-helpers
- version: f72c04f1d8e71959a6d103f808c50ccbad79b9fd
- subpackages:
- - client
- - credentials
-- name: github.com/docker/go-connections
- version: ecb4cb2dd420ada7df7f2593d6c25441f65f69f2
- subpackages:
- - nat
- - sockets
- - tlsconfig
-- name: github.com/docker/go-units
- version: 8a7beacffa3009a9ac66bad506b18ffdd110cf97
-- name: github.com/docker/libtrust
- version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
-- name: github.com/golang/protobuf
- version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a
- subpackages:
- - proto
-- name: github.com/gorilla/context
- version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
-- name: github.com/gorilla/mux
- version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
-- name: github.com/inconshreveable/mousetrap
- version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
-- name: github.com/johntdyer/slack-go
- version: 95fac1160b220c5abcf8b0ef88e9c3cb213c09f4
-- name: github.com/johntdyer/slackrus
- version: 3992f319fd0ac349483279ef74742cd787841cba
-- name: github.com/mattn/go-shellwords
- version: f4e566c536cf69158e808ec28ef4182a37fdc981
-- name: github.com/Microsoft/go-winio
- version: fff283ad5116362ca252298cfc9b95828956d85d
-- name: github.com/opencontainers/runc
- version: 9df8b306d01f59d3a8029be411de015b7304dd8f
- repo: https://github.com/docker/runc.git
- subpackages:
- - libcontainer/configs
- - libcontainer/devices
- - libcontainer/system
- - libcontainer/user
-- name: github.com/opencontainers/runtime-spec
- version: 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
- subpackages:
- - specs-go
-- name: github.com/pkg/errors
- version: 839d9e913e063e28dfd0e6c7b7512793e0a48be9
-- name: github.com/pmezard/go-difflib
- version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
- subpackages:
- - difflib
-- name: github.com/robfig/cron
- version: 9585fd555638e77bba25f25db5c44b41f264aeb7
-- name: github.com/Sirupsen/logrus
- version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
- repo: https://github.com/sirupsen/logrus.git
-- name: github.com/sirupsen/logrus
- version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
-- name: github.com/spf13/cobra
- version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
- repo: https://github.com/dnephin/cobra.git
-- name: github.com/spf13/pflag
- version: dabebe21bf790f782ea4c7bbd2efc430de182afd
-- name: github.com/stretchr/objx
- version: cbeaeb16a013161a98496fad62933b1d21786672
-- name: github.com/stretchr/testify
- version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
- subpackages:
- - assert
- - mock
-- name: github.com/urfave/cli
- version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
-- name: github.com/vbatts/tar-split
- version: d3f1b54304d656376e58f9406a9cb4775799a357
- subpackages:
- - archive/tar
- - tar/asm
- - tar/storage
-- name: golang.org/x/net
- version: 2beffdc2e92c8a3027590f898fe88f69af48a3f8
- repo: https://github.com/tonistiigi/net.git
- subpackages:
- - context
- - context/ctxhttp
- - http2
- - http2/hpack
- - internal/timeseries
- - proxy
- - trace
-- name: golang.org/x/sys
- version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
- subpackages:
- - unix
- - windows
-- name: google.golang.org/grpc
- version: b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21
- subpackages:
- - codes
- - credentials
- - grpclog
- - internal
- - metadata
- - naming
- - peer
- - transport
-testImports: []
diff --git a/glide.yaml b/glide.yaml
deleted file mode 100644
index bd9ed6d..0000000
--- a/glide.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-package: github.com/containrrr/watchtower
-import:
-- package: github.com/sirupsen/logrus
- version: ~0.11.x
-- package: github.com/Sirupsen/logrus
- repo: https://github.com/sirupsen/logrus.git
- version: ~0.11.x
-- package: github.com/docker/docker
- version: ~1.13.x
- subpackages:
- - api/types
- - api/types/container
- - api/types/network
- - api/types/reference
- - cli/command
- - cliconfig
- - cliconfig/configfile
- - cliconfig/credentials
- - client
-- package: github.com/stretchr/testify
- version: ~1.1.4
- subpackages:
- - mock
-- package: github.com/urfave/cli
- version: ~1.19.1
-- package: golang.org/x/net
- repo: https://github.com/tonistiigi/net.git
- subpackages:
- - context
-- package: github.com/robfig/cron
- version: 9585fd555638e77bba25f25db5c44b41f264aeb7
-- package: github.com/johntdyer/slackrus
- version: 3992f31
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..005ed9b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,53 @@
+module github.com/containrrr/watchtower
+
+go 1.12
+
+require (
+ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
+ github.com/Microsoft/go-winio v0.4.12
+ github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
+ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
+ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
+ github.com/davecgh/go-spew v1.1.1
+ github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
+ github.com/docker/distribution v2.7.1+incompatible
+ github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
+ github.com/docker/docker-credential-helpers v0.6.1
+ github.com/docker/go v1.5.1-1
+ github.com/docker/go-connections v0.4.0
+ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82
+ github.com/docker/go-units v0.3.3
+ github.com/gogo/protobuf v1.2.1
+ github.com/golang/protobuf v1.3.1
+ github.com/gorilla/mux v1.7.0
+ github.com/hashicorp/go-version v1.1.0
+ github.com/inconshreveable/mousetrap v1.0.0
+ github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22
+ github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
+ github.com/konsorten/go-windows-terminal-sequences v1.0.2
+ github.com/matttproud/golang_protobuf_extensions v1.0.1
+ github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa
+ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
+ github.com/opencontainers/go-digest v1.0.0-rc1
+ github.com/opencontainers/image-spec v1.0.1
+ github.com/opencontainers/runc v0.1.1
+ github.com/pkg/errors v0.8.1
+ github.com/pmezard/go-difflib v1.0.0
+ github.com/prometheus/client_golang v0.9.2
+ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
+ github.com/prometheus/common v0.2.0
+ github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872
+ github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
+ github.com/sirupsen/logrus v1.4.1
+ github.com/spf13/cobra v0.0.3
+ github.com/spf13/pflag v1.0.3
+ github.com/stretchr/objx v0.1.1
+ github.com/stretchr/testify v1.3.0
+ github.com/theupdateframework/notary v0.6.1
+ github.com/urfave/cli v1.20.0
+ golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
+ golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b
+ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
+ google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
+ google.golang.org/grpc v1.19.1
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..b7346ab
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,148 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
+github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
+github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
+github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
+github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/cli v0.0.0-20190327152802-57b27434ea29 h1:ciaXDHaWQda0nvevWqcjtXX/buQY3e0lga1vq8Batq0=
+github.com/docker/cli v0.0.0-20190327152802-57b27434ea29/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
+github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4 h1:34LfsqlE2kEvmGP9qbRoPvOWkmluYGzmlvWVTzwvT0A=
+github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
+github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
+github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k=
+github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA=
+github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
+github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
+github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
+github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
+github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
+github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY=
+github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
+github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
+github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
+github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 h1:0aNv3xC7DmQoy1/x1sMh18g+fihWW68LL13i8ao9kl4=
+github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
+github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
+github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
+github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692 h1:GRhHqDOgeDr6QDTtq9gn2O4iKvm5dsbfqD/TXb0KLX0=
+golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b h1:/zjbcJPEGAyu6Is/VBOALsgdi4z9+kz/Vtdm6S+beD0=
+golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2 h1:8FyEBtGg6Px24p+H2AkuVWqhj4+R9fo+fZD17mg+lzk=
+google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
+google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/goreleaser.yml b/goreleaser.yml
index c0ea4c3..20c763e 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -1,39 +1,16 @@
-# Build customization
build:
- # Path to main.go file.
- # Default is `main.go`
main: ./main.go
-
- # GOOS list to build in.
- # For more info refer to https://golang.org/doc/install/source#environment
- # Defaults are darwin and linux
+ binary: watchtower
goos:
- linux
- windows
-
- # GOARCH to build in.
- # For more info refer to https://golang.org/doc/install/source#environment
- # Defaults are 386 and amd64
goarch:
- amd64
- arm
- arm64
-
-# Archive customization
archive:
- # You can change the name of the archive.
- # This is parsed with Golang template engine and the following variables.
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
-
- # Archive format. Valid options are `tar.gz` and `zip`.
- # Default is `zip`
format: tar.gz
-
- # Replacements for GOOS and GOARCH on the archive name.
- # The keys should be valid GOOS or GOARCH values followed by your custom
- # replacements.
- # By default, `replacements` replace GOOS and GOARCH values with valid outputs
- # of `uname -s` and `uname -m` respectively.
replacements:
arm: armhf
arm64: arm64v8
@@ -41,42 +18,42 @@ archive:
386: 386
darwin: macOS
linux: linux
-
format_overrides:
- goos: windows
format: zip
-
- # Additional files you want to add to the archive.
- # Defaults are any files matching `LICENCE*`, `LICENSE*`,
- # `README*` and `CHANGELOG*` (case-insensitive)
files:
- LICENSE.md
-
dockers:
-
goos: linux
goarch: amd64
goarm: ''
- binary: watchtower
- image: containrrr/watchtower
dockerfile: dockerfile/amd64/Dockerfile
- tag_templates:
- - '{{ .Version }}'
+ build_flag_templates:
+ - "--label=com.centurylinklabs.watchtower=true"
+ image_templates:
+ - containrrr/watchtower:{{ .Version }}
+ binaries:
+ - watchtower
-
goos: linux
goarch: arm
goarm: 6
- binary: watchtower
- image: containrrr/watchtower
dockerfile: dockerfile/armhf/Dockerfile
- tag_templates:
- - 'armhf-{{ .Version }}'
+ build_flag_templates:
+ - "--label=com.centurylinklabs.watchtower=true"
+ image_templates:
+ - containrrr/watchtower:armhf-{{ .Version }}
+ binaries:
+ - watchtower
-
goos: linux
goarch: arm64
goarm: ''
- binary: watchtower
- image: containrrr/watchtower
dockerfile: dockerfile/arm64v8/Dockerfile
- tag_templates:
- - 'arm64v8-{{ .Version }}'
+ build_flag_templates:
+ - "--label=com.centurylinklabs.watchtower=true"
+ image_templates:
+ - containrrr/watchtower:arm64v8-{{ .Version }}
+ binaries:
+ - watchtower
\ No newline at end of file
diff --git a/notifications/smtp.go b/notifications/smtp.go
index d0f07f8..82954bc 100644
--- a/notifications/smtp.go
+++ b/notifications/smtp.go
@@ -1,3 +1,4 @@
+// Package notifications ...
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license.
From 6157c50e4fe1fab15ca8022f3e82c306e73d6d35 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 6 Apr 2019 14:05:01 +0200
Subject: [PATCH 070/761] fix some minor issues
---
.circleci/config.yml | 2 +-
container/client.go | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 82031be..bead463 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -93,7 +93,7 @@ jobs:
./goreleaser -v
- run:
name: Execute goreleaser
- command: ../goreleaser --snapshot --skip-publish --debug
+ command: CGO_ENABLED=${CGO_ENABLED:-0} ../goreleaser --snapshot --skip-publish --debug
publishing:
executor: go
steps:
diff --git a/container/client.go b/container/client.go
index 47e4776..62bce6c 100644
--- a/container/client.go
+++ b/container/client.go
@@ -187,8 +187,8 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
auth, err := EncodedAuth(imageName)
- log.Infof("Got auth value: %s", auth)
- log.Infof("Got image name: %s", imageName)
+ log.Debugf("Got auth value: %s", auth)
+ log.Debugf("Got image name: %s", imageName)
if err != nil {
log.Debugf("Error loading authentication credentials %s", err)
return false, err
From 7db0aed2a5235a471b5b67f7d57b903c9c882ff0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 6 Apr 2019 14:27:50 +0200
Subject: [PATCH 071/761] print to log if multiple watchtower instances are
detected
---
actions/check.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/actions/check.go b/actions/check.go
index 2517dc2..a95133e 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -3,6 +3,8 @@ package actions
import (
"sort"
+ log "github.com/sirupsen/logrus"
+
"github.com/containrrr/watchtower/container"
)
@@ -17,6 +19,7 @@ func CheckPrereqs(client container.Client, cleanup bool) error {
}
if len(containers) > 1 {
+ log.Info("Found multiple running watchtower instances. Cleaning up")
sort.Sort(container.ByCreated(containers))
// Iterate over all containers execept the last one
From bac518a4413570770a0215a185a4988e63415cf6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 14:52:43 +0200
Subject: [PATCH 072/761] add a logo and spice up the top part of the readme
---
README.md | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index c0dfbf2..f32d456 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,29 @@
-# Watchtower
+
+
+
+
+ Watchtower
+
-[](https://circleci.com/gh/containrrr/watchtower)
-[](https://godoc.org/github.com/containrrr/watchtower)
-[](https://microbadger.com/images/containrrr/watchtower "Get your own image badge on microbadger.com")
-[](https://goreportcard.com/report/github.com/containrrr/watchtower)
-[](https://houndci.com)
-
-A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.
+
+ A process for automating Docker container base image updates.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## Overview
From 438c90f821198de3dcd8533faf4cbe585cce1d00 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 14:53:29 +0200
Subject: [PATCH 073/761] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f32d456..a671490 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ For example, let's say you were running watchtower along with an instance of *ce
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
-6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
+6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
```
Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
From d29b6cf93eeda8cc502ebc3a1d3bdd03b1efcc32 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 15:52:56 +0200
Subject: [PATCH 074/761] add a monitor only flag
---
actions/update.go | 25 +++++++++++++++++++------
main.go | 16 +++++++++++++++-
2 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/actions/update.go b/actions/update.go
index e631b1a..27fe144 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -12,14 +12,22 @@ var (
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
+type UpdateParams struct {
+ Filter container.Filter
+ Cleanup bool
+ NoRestart bool
+ Timeout time.Duration
+ MonitorOnly bool
+}
+
// Update looks at the running Docker containers to see if any of the images
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
-func Update(client container.Client, filter container.Filter, cleanup bool, noRestart bool, timeout time.Duration) error {
+func Update(client container.Client, params UpdateParams) error {
log.Debug("Checking containers for updated images")
- containers, err := client.ListContainers(filter)
+ containers, err := client.ListContainers(params.Filter)
if err != nil {
return err
}
@@ -27,7 +35,8 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
for i, container := range containers {
stale, err := client.IsContainerStale(container)
if err != nil {
- log.Infof("Unable to update container %s, err='%s'. Proceeding to next.", containers[i].Name(), err)
+ log.Infof("Unable to update container %s. Proceeding to next.", containers[i].Name())
+ log.Debug(err)
stale = false
}
containers[i].Stale = stale
@@ -40,6 +49,10 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
checkDependencies(containers)
+ if params.MonitorOnly {
+ return nil
+ }
+
// Stop stale containers in reverse order
for i := len(containers) - 1; i >= 0; i-- {
container := containers[i]
@@ -50,7 +63,7 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
}
if container.Stale {
- if err := client.StopContainer(container, timeout); err != nil {
+ if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
}
@@ -70,13 +83,13 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
}
}
- if !noRestart {
+ if !params.NoRestart {
if err := client.StartContainer(container); err != nil {
log.Error(err)
}
}
- if cleanup {
+ if params.Cleanup {
client.RemoveImage(container)
}
}
diff --git a/main.go b/main.go
index c686b65..fc853fe 100644
--- a/main.go
+++ b/main.go
@@ -29,6 +29,7 @@ var (
scheduleSpec string
cleanup bool
noRestart bool
+ monitorOnly bool
enableLabel bool
notifier *notifications.Notifier
timeout time.Duration
@@ -171,6 +172,11 @@ func main() {
Usage: "The MSTeams notifier will try to extract log entry fields as MSTeams message facts",
EnvVar: "WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA",
},
+ cli.BoolFlag{
+ Name: "monitor-only",
+ Usage: "Will only monitor for new images, not update the containers",
+ EnvVar: "WATCHTOWER_MONITOR_ONLY",
+ },
}
if err := app.Run(os.Args); err != nil {
@@ -196,6 +202,7 @@ func before(c *cli.Context) error {
cleanup = c.GlobalBool("cleanup")
noRestart = c.GlobalBool("no-restart")
+ monitorOnly = c.GlobalBool("monitor-only")
timeout = c.GlobalDuration("stop-timeout")
if timeout < 0 {
log.Fatal("Please specify a positive value for timeout value.")
@@ -234,7 +241,14 @@ func start(c *cli.Context) error {
case v := <-tryLockSem:
defer func() { tryLockSem <- v }()
notifier.StartNotification()
- if err := actions.Update(client, filter, cleanup, noRestart, timeout); err != nil {
+ updateParams := actions.UpdateParams{
+ Filter: filter,
+ Cleanup: cleanup,
+ NoRestart: noRestart,
+ Timeout: timeout,
+ MonitorOnly: monitorOnly,
+ }
+ if err := actions.Update(client, updateParams); err != nil {
log.Println(err)
}
notifier.SendNotification()
From 66f38bd302122c219f32a08c4049e0de678c944d Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 15:55:16 +0200
Subject: [PATCH 075/761] fix linting errors
---
actions/update.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/actions/update.go b/actions/update.go
index 27fe144..a18ea4c 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -12,6 +12,7 @@ var (
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
+// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
Filter container.Filter
Cleanup bool
From 59eb978b5f0917957c37b5a985a5c30a1e675cc5 Mon Sep 17 00:00:00 2001
From: Prashanth Joseph Babu
Date: Wed, 22 Aug 2018 17:47:16 +0530
Subject: [PATCH 076/761] 32bit support
---
dockerfile/386/Dockerfile | 20 ++++++++++++++++++++
goreleaser.yml | 10 ++++++++++
2 files changed, 30 insertions(+)
create mode 100644 dockerfile/386/Dockerfile
diff --git a/dockerfile/386/Dockerfile b/dockerfile/386/Dockerfile
new file mode 100644
index 0000000..13b19e8
--- /dev/null
+++ b/dockerfile/386/Dockerfile
@@ -0,0 +1,20 @@
+#
+# Alpine image to get some needed data
+#
+FROM alpine:latest as alpine
+RUN apk add --no-cache \
+ ca-certificates \
+ tzdata
+
+#
+# Image
+#
+FROM scratch
+LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other containers
+COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
+
+COPY watchtower /
+ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/goreleaser.yml b/goreleaser.yml
index 20c763e..a81dbf8 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -6,6 +6,7 @@ build:
- windows
goarch:
- amd64
+ - 386
- arm
- arm64
archive:
@@ -35,6 +36,15 @@ dockers:
- containrrr/watchtower:{{ .Version }}
binaries:
- watchtower
+ -
+ goos: linux
+ goarch: 386
+ goarm: ''
+ binary: watchtower
+ image: v2tec/watchtower
+ dockerfile: dockerfile/386/Dockerfile
+ tag_templates:
+ - '386-{{ .Version }}'
-
goos: linux
goarch: arm
From 0cd6e6954afee67ddd0418bfa04b4aed78dcb7ae Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 16:51:37 +0200
Subject: [PATCH 077/761] minor changes to make it work with new goreleaser
version
---
dockerfile/amd64/Dockerfile | 7 -------
dockerfile/arm64v8/Dockerfile | 7 -------
dockerfile/armhf/Dockerfile | 7 -------
dockerfile/{386 => i386}/Dockerfile | 7 -------
goreleaser.yml | 18 ++++++++++--------
5 files changed, 10 insertions(+), 36 deletions(-)
rename dockerfile/{386 => i386}/Dockerfile (79%)
diff --git a/dockerfile/amd64/Dockerfile b/dockerfile/amd64/Dockerfile
index 13b19e8..7594771 100644
--- a/dockerfile/amd64/Dockerfile
+++ b/dockerfile/amd64/Dockerfile
@@ -1,18 +1,11 @@
-#
-# Alpine image to get some needed data
-#
FROM alpine:latest as alpine
RUN apk add --no-cache \
ca-certificates \
tzdata
-#
-# Image
-#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
-# copy files from other containers
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
diff --git a/dockerfile/arm64v8/Dockerfile b/dockerfile/arm64v8/Dockerfile
index 13b19e8..7594771 100644
--- a/dockerfile/arm64v8/Dockerfile
+++ b/dockerfile/arm64v8/Dockerfile
@@ -1,18 +1,11 @@
-#
-# Alpine image to get some needed data
-#
FROM alpine:latest as alpine
RUN apk add --no-cache \
ca-certificates \
tzdata
-#
-# Image
-#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
-# copy files from other containers
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
diff --git a/dockerfile/armhf/Dockerfile b/dockerfile/armhf/Dockerfile
index 13b19e8..7594771 100644
--- a/dockerfile/armhf/Dockerfile
+++ b/dockerfile/armhf/Dockerfile
@@ -1,18 +1,11 @@
-#
-# Alpine image to get some needed data
-#
FROM alpine:latest as alpine
RUN apk add --no-cache \
ca-certificates \
tzdata
-#
-# Image
-#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
-# copy files from other containers
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
diff --git a/dockerfile/386/Dockerfile b/dockerfile/i386/Dockerfile
similarity index 79%
rename from dockerfile/386/Dockerfile
rename to dockerfile/i386/Dockerfile
index 13b19e8..7594771 100644
--- a/dockerfile/386/Dockerfile
+++ b/dockerfile/i386/Dockerfile
@@ -1,18 +1,11 @@
-#
-# Alpine image to get some needed data
-#
FROM alpine:latest as alpine
RUN apk add --no-cache \
ca-certificates \
tzdata
-#
-# Image
-#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
-# copy files from other containers
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
diff --git a/goreleaser.yml b/goreleaser.yml
index a81dbf8..695f59f 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -6,7 +6,7 @@ build:
- windows
goarch:
- amd64
- - 386
+ - i386
- arm
- arm64
archive:
@@ -16,7 +16,7 @@ archive:
arm: armhf
arm64: arm64v8
amd64: amd64
- 386: 386
+ 386: i386
darwin: macOS
linux: linux
format_overrides:
@@ -38,13 +38,15 @@ dockers:
- watchtower
-
goos: linux
- goarch: 386
+ goarch: i386
goarm: ''
- binary: watchtower
- image: v2tec/watchtower
- dockerfile: dockerfile/386/Dockerfile
- tag_templates:
- - '386-{{ .Version }}'
+ dockerfile: dockerfile/i386/Dockerfile
+ build_flag_templates:
+ - "--label=com.centurylinklabs.watchtower=true"
+ image_templates:
+ - containrrr/watchtower:i386-{{ .Version }}
+ binaries:
+ - watchtower
-
goos: linux
goarch: arm
From bf9e1ed1994c44fbbfa19b37f227de7e20bc556b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 16:56:51 +0200
Subject: [PATCH 078/761] remove erroneuos i in goreleaser
---
goreleaser.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/goreleaser.yml b/goreleaser.yml
index 695f59f..d581ae3 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -38,7 +38,7 @@ dockers:
- watchtower
-
goos: linux
- goarch: i386
+ goarch: 386
goarm: ''
dockerfile: dockerfile/i386/Dockerfile
build_flag_templates:
From 01ce9a145f4792e0d2a15db3e0533d6a02b073c6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 16:57:35 +0200
Subject: [PATCH 079/761] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index a671490..67db06d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
Watchtower
From ebb416819c6c858f72030207c0b689d470dd5bb4 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 17:00:44 +0200
Subject: [PATCH 080/761] remove (another) erroneuos i in goreleaser
---
goreleaser.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/goreleaser.yml b/goreleaser.yml
index d581ae3..54ef038 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -6,7 +6,7 @@ build:
- windows
goarch:
- amd64
- - i386
+ - 386
- arm
- arm64
archive:
@@ -16,7 +16,7 @@ archive:
arm: armhf
arm64: arm64v8
amd64: amd64
- 386: i386
+ 386: 386
darwin: macOS
linux: linux
format_overrides:
From da41afc1d763e8a1c088120fb576068740a71c2f Mon Sep 17 00:00:00 2001
From: Mario Tacke
Date: Sun, 7 Apr 2019 08:38:20 -0700
Subject: [PATCH 081/761] Add Slack Channel, IconEmoji, and IconURL options
(#241)
---
README.md | 8 ++++++++
main.go | 15 +++++++++++++++
notifications/slack.go | 3 +++
3 files changed, 26 insertions(+)
diff --git a/README.md b/README.md
index 67db06d..06868e6 100644
--- a/README.md
+++ b/README.md
@@ -274,6 +274,11 @@ Additionally, you should set the Slack webhook url using the `--notification-sla
By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
+Other, optional, variables include:
+- `--notification-slack-channel` (env. `WATCHTOWER_NOTIFICATION_SLACK_CHANNEL`): A string which overrides the webhook's default channel. Example: #my-custom-channel.
+- `--notification-slack-icon-emoji` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI`): An [emoji code](https://www.webpagefx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.
+- `--notification-slack-icon-url` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_URL`): An icon image URL string to use in place of the default icon.
+
Example:
```bash
@@ -283,6 +288,9 @@ docker run -d \
-e WATCHTOWER_NOTIFICATIONS=slack \
-e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
-e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
+ -e WATCHTOWER_NOTIFICATION_SLACK_CHANNEL=#my-custom-channel \
+ -e WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI=:whale: \
+ -e WATCHTOWER_NOTIFICATION_SLACK_ICON_URL= \
containrrr/watchtower
```
diff --git a/main.go b/main.go
index fc853fe..8a34d67 100644
--- a/main.go
+++ b/main.go
@@ -162,6 +162,21 @@ func main() {
EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER",
Value: "watchtower",
},
+ cli.StringFlag{
+ Name: "notification-slack-channel",
+ Usage: "A string which overrides the webhook's default channel. Example: #my-custom-channel",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_CHANNEL",
+ },
+ cli.StringFlag{
+ Name: "notification-slack-icon-emoji",
+ Usage: "An emoji code string to use in place of the default icon",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI",
+ },
+ cli.StringFlag{
+ Name: "notification-slack-icon-url",
+ Usage: "An icon image URL string to use in place of the default icon",
+ EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_ICON_URL",
+ },
cli.StringFlag{
Name: "notification-msteams-hook",
Usage: "The MSTeams WebHook URL to send notifications to",
diff --git a/notifications/slack.go b/notifications/slack.go
index dd44f29..08f8d3b 100644
--- a/notifications/slack.go
+++ b/notifications/slack.go
@@ -19,6 +19,9 @@ func newSlackNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifie
SlackrusHook: slackrus.SlackrusHook{
HookURL: c.GlobalString("notification-slack-hook-url"),
Username: c.GlobalString("notification-slack-identifier"),
+ Channel: c.GlobalString("notification-slack-channel"),
+ IconEmoji: c.GlobalString("notification-slack-icon-emoji"),
+ IconURL: c.GlobalString("notification-slack-icon-url"),
AcceptedLevels: acceptedLogLevels,
},
}
From 18a0085fb5182a958461f876d24ecabfac4a7a7d Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 18:30:01 +0200
Subject: [PATCH 082/761] add new flag to readme
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 06868e6..1419ac7 100644
--- a/README.md
+++ b/README.md
@@ -130,6 +130,7 @@ docker run --rm containrrr/watchtower --help
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
+* `--monitor-only` Will only monitor for new images, not update the containers.
* `--help` Show documentation about the supported flags.
See below for options used to configure notifications.
From 790e487a55195bedc301be2e70ac3669653cfe62 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 20:34:19 +0200
Subject: [PATCH 083/761] add licensing badge and latest semver badge
---
README.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/README.md b/README.md
index 1419ac7..6c6e46f 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,12 @@
+
+
+
+
+
+
From 859cd79b589da19d95359582ad048ee0654c5e82 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 21:47:50 +0200
Subject: [PATCH 084/761] Create CODE_OF_CONDUCT.md
---
CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 CODE_OF_CONDUCT.md
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..6a76fea
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team. All complaints will be reviewed and
+investigated and will result in a response that is deemed necessary and
+appropriate to the circumstances. The project team is obligated to maintain
+confidentiality with regard to the reporter of an incident. Further details
+of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
From 1e6c566915a24847215f7ad7776f35b4b4caa184 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 21:51:30 +0200
Subject: [PATCH 085/761] Update issue templates
---
.github/ISSUE_TEMPLATE/bug_report.md | 34 +++++++++++++++++++++++
.github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++
.github/ISSUE_TEMPLATE/question.md | 10 +++++++
3 files changed, 64 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
create mode 100644 .github/ISSUE_TEMPLATE/question.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..a4a8eb6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Environment**
+ - Platform
+ - Architecture
+ - Docker version
+
+**Logs from running watchtower with the `--debug` option**
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..11fc491
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..83d3095
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,10 @@
+---
+name: Question
+about: Describe this issue template's purpose here.
+title: ''
+labels: question
+assignees: ''
+
+---
+
+
From 61d1ab1c49e1c1efc69ca4dcbe0a7f178c982ee7 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 7 Apr 2019 21:54:32 +0200
Subject: [PATCH 086/761] Update question.md
---
.github/ISSUE_TEMPLATE/question.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 83d3095..9fa20c6 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -1,6 +1,6 @@
---
name: Question
-about: Describe this issue template's purpose here.
+about: Questions regarding usage or specific use cases.
title: ''
labels: question
assignees: ''
From 5513f95bde8d5e1d355f904be81eb2116d846222 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 8 Apr 2019 20:35:23 +0200
Subject: [PATCH 087/761] fix readme
---
README.md | 78 +++++++++++++++++++++++++++++--------------------------
1 file changed, 41 insertions(+), 37 deletions(-)
diff --git a/README.md b/README.md
index e3a9be7..affd130 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Watchtower is an application that will monitor your running Docker containers an
With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially.
-For example, let's say you were running watchtower along with an instance of *centurylink/wetty-cli* image:
+For example, let's say you were running watchtower along with an instance of _centurylink/wetty-cli_ image:
```bash
$ docker ps
@@ -46,13 +46,13 @@ CONTAINER ID IMAGE STATUS PORTS
6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
```
-Every few minutes watchtower will pull the latest *centurylink/wetty-cli* image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
+Every few minutes watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
## Usage
Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
-Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount */var/run/docker.sock* into the container with the -v flag when you run it.
+Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the -v flag when you run it.
Run the `watchtower` container with the following command:
@@ -67,6 +67,7 @@ If pulling images from private Docker registries, supply registry authentication
or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
Passing environment variables:
+
```bash
docker run -d \
--name watchtower \
@@ -75,9 +76,11 @@ docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower container_to_watch --debug
```
+
Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
Mounting the host's docker config file:
+
```bash
docker run -d \
--name watchtower \
@@ -117,12 +120,12 @@ docker run -d \
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other running containers will be ignored.
-If you do not want watchtower to run as a daemon you can pass a oneshot flag and remove the watchtower container after it's execution.
+If you do not want watchtower to run as a daemon you can pass a run-once flag and remove the watchtower container after it's execution.
```bash
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v2tec/watchtower --oneshot nginx redis
+v2tec/watchtower --run-once nginx redis
```
In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
@@ -137,18 +140,18 @@ Any of the options described below can be passed to the watchtower process by se
docker run --rm containrrr/watchtower --help
```
-* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
-* `--oneshot` Run an update attempt against a container name list one time immediately and exit.
-* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
-* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *" `
-* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
-* `--stop-timeout` Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
-* `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
-* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
-* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
-* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
-* `--monitor-only` Will only monitor for new images, not update the containers.
-* `--help` Show documentation about the supported flags.
+- `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
+- `--run-once` Run an update attempt against a container name list one time immediately and exit.
+- `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
+- `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
+- `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
+- `--stop-timeout` Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
+- `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
+- `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
+- `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
+- `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
+- `--monitor-only` Will only monitor for new images, not update the containers.
+- `--help` Show documentation about the supported flags.
See below for options used to configure notifications.
@@ -156,12 +159,12 @@ See below for options used to configure notifications.
Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
-For example, imagine you were running a *mysql* container and a *wordpress* container which had been linked to the *mysql* container. If watchtower were to detect that the *mysql* container required an update, it would first shut down the linked *wordpress* container followed by the *mysql* container. When restarting the containers it would handle *mysql* first and then *wordpress* to ensure that the link continued to work.
+For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
## Stopping Containers
When watchtower detects that a running container needs to be updated it will stop the container by sending it a SIGTERM signal.
-If your container should be shutdown with a different signal you can communicate this to watchtower by setting a label named *com.centurylinklabs.watchtower.stop-signal* with the value of the desired signal.
+If your container should be shutdown with a different signal you can communicate this to watchtower by setting a label named _com.centurylinklabs.watchtower.stop-signal_ with the value of the desired signal.
This label can be coded directly into your image by using the `LABEL` instruction in your Dockerfile:
@@ -179,7 +182,7 @@ docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimag
By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
-If you need to exclude some containers, set the *com.centurylinklabs.watchtower.enable* label to `false`.
+If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`.
```docker
LABEL com.centurylinklabs.watchtower.enable="false"
@@ -191,7 +194,7 @@ Or, it can be specified as part of the `docker run` command line:
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
```
-If you need to only include only some containers, pass the --label-enable flag on startup and set the *com.centurylinklabs.watchtower.enable* label with a value of true for the containers you want to watch.
+If you need to only include only some containers, pass the --label-enable flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of true for the containers you want to watch.
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
@@ -222,13 +225,13 @@ docker run -d \
containrrr/watchtower
```
-Note in both of the examples above that it is unnecessary to mount the */var/run/docker.sock* into the watchtower container.
+Note in both of the examples above that it is unnecessary to mount the _/var/run/docker.sock_ into the watchtower container.
### Secure Connections
-Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used *docker-machine* to provision your remote Docker host, you simply need to volume mount the certificates generated by *docker-machine* into the watchtower container and optionally specify `--tlsverify` flag.
+Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used _docker-machine_ to provision your remote Docker host, you simply need to volume mount the certificates generated by _docker-machine_ into the watchtower container and optionally specify `--tlsverify` flag.
-The *docker-machine* certificates for a particular host can be located by executing the `docker-machine env` command for the desired host (note the values for the `DOCKER_HOST` and `DOCKER_CERT_PATH` environment variables that are returned from this command). The directory containing the certificates for the remote host needs to be mounted into the watchtower container at */etc/ssl/docker*.
+The _docker-machine_ certificates for a particular host can be located by executing the `docker-machine env` command for the desired host (note the values for the `DOCKER_HOST` and `DOCKER_CERT_PATH` environment variables that are returned from this command). The directory containing the certificates for the remote host needs to be mounted into the watchtower container at _/etc/ssl/docker_.
With the certificates mounted into the watchtower container you need to specify the `--tlsverify` flag to enable verification of the certificate:
@@ -242,32 +245,32 @@ docker run -d \
## Updating Watchtower
-If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *containrrr/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
+If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted _/var/run/docker.sock_ into the watchtower container) then it has the ability to update itself. If a new version of the _containrrr/watchtower_ image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
## Notifications
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
-* `email` to send notifications via e-mail
-* `slack` to send notifications through a Slack webhook
-* `msteams` to send notifications via MSTeams webhook
+- `email` to send notifications via e-mail
+- `slack` to send notifications through a Slack webhook
+- `msteams` to send notifications via MSTeams webhook
### Settings
-* `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
+- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
### Notifications via E-Mail
To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
-* `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
-* `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
-* `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
-* `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
-* `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
-* `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
-* `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
+- `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
+- `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
+- `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
+- `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
+- `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
+- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
+- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
Example:
@@ -293,6 +296,7 @@ Additionally, you should set the Slack webhook url using the `--notification-sla
By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
Other, optional, variables include:
+
- `--notification-slack-channel` (env. `WATCHTOWER_NOTIFICATION_SLACK_CHANNEL`): A string which overrides the webhook's default channel. Example: #my-custom-channel.
- `--notification-slack-icon-emoji` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI`): An [emoji code](https://www.webpagefx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.
- `--notification-slack-icon-url` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_URL`): An icon image URL string to use in place of the default icon.
@@ -318,7 +322,7 @@ To receive notifications in MSTeams channel, add `msteams` to the `--notificatio
Additionally, you should set the MSTeams webhook url using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
-MSTeams notifier could send keys/values filled by ```log.WithField``` or ```log.WithFields``` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
+MSTeams notifier could send keys/values filled by `log.WithField` or `log.WithFields` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
Example:
From e27d41505acbff439100f59c72e19f50d4fdd05d Mon Sep 17 00:00:00 2001
From: Florian
Date: Wed, 10 Apr 2019 17:31:30 +0200
Subject: [PATCH 088/761] =?UTF-8?q?Updated=20README.md:=20v2tech=20?=
=?UTF-8?q?=E2=86=92=20containrrr?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index affd130..73474f6 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ If you do not want watchtower to run as a daemon you can pass a run-once flag an
```bash
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v2tec/watchtower --run-once nginx redis
+containrrr/watchtower --run-once nginx redis
```
In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
From 1f9099770b25b26b66bef355f4d66dda492db06f Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 10 Apr 2019 21:48:31 +0200
Subject: [PATCH 089/761] reduce to one dockerfile as they have the exact same
content
---
dockerfile/amd64/Dockerfile => Dockerfile | 9 +++++++--
dockerfile/arm64v8/Dockerfile | 13 -------------
dockerfile/armhf/Dockerfile | 13 -------------
dockerfile/i386/Dockerfile | 13 -------------
goreleaser.yml | 16 ++++------------
5 files changed, 11 insertions(+), 53 deletions(-)
rename dockerfile/amd64/Dockerfile => Dockerfile (52%)
delete mode 100644 dockerfile/arm64v8/Dockerfile
delete mode 100644 dockerfile/armhf/Dockerfile
delete mode 100644 dockerfile/i386/Dockerfile
diff --git a/dockerfile/amd64/Dockerfile b/Dockerfile
similarity index 52%
rename from dockerfile/amd64/Dockerfile
rename to Dockerfile
index 7594771..d3a324a 100644
--- a/dockerfile/amd64/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,5 @@
FROM alpine:latest as alpine
+
RUN apk add --no-cache \
ca-certificates \
tzdata
@@ -6,8 +7,12 @@ RUN apk add --no-cache \
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
-COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
-COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=alpine \
+ /etc/ssl/certs/ca-certificates.crt \
+ /etc/ssl/certs/ca-certificates.crt
+COPY --from=alpine \
+ /usr/share/zoneinfo \
+ /usr/share/zoneinfo
COPY watchtower /
ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/arm64v8/Dockerfile b/dockerfile/arm64v8/Dockerfile
deleted file mode 100644
index 7594771..0000000
--- a/dockerfile/arm64v8/Dockerfile
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM alpine:latest as alpine
-RUN apk add --no-cache \
- ca-certificates \
- tzdata
-
-FROM scratch
-LABEL "com.centurylinklabs.watchtower"="true"
-
-COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
-COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
-
-COPY watchtower /
-ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/armhf/Dockerfile b/dockerfile/armhf/Dockerfile
deleted file mode 100644
index 7594771..0000000
--- a/dockerfile/armhf/Dockerfile
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM alpine:latest as alpine
-RUN apk add --no-cache \
- ca-certificates \
- tzdata
-
-FROM scratch
-LABEL "com.centurylinklabs.watchtower"="true"
-
-COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
-COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
-
-COPY watchtower /
-ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/dockerfile/i386/Dockerfile b/dockerfile/i386/Dockerfile
deleted file mode 100644
index 7594771..0000000
--- a/dockerfile/i386/Dockerfile
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM alpine:latest as alpine
-RUN apk add --no-cache \
- ca-certificates \
- tzdata
-
-FROM scratch
-LABEL "com.centurylinklabs.watchtower"="true"
-
-COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
-COPY --from=alpine /usr/share/zoneinfo /usr/share/zoneinfo
-
-COPY watchtower /
-ENTRYPOINT ["/watchtower"]
\ No newline at end of file
diff --git a/goreleaser.yml b/goreleaser.yml
index 54ef038..2db606c 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -29,9 +29,7 @@ dockers:
goos: linux
goarch: amd64
goarm: ''
- dockerfile: dockerfile/amd64/Dockerfile
- build_flag_templates:
- - "--label=com.centurylinklabs.watchtower=true"
+ dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:{{ .Version }}
binaries:
@@ -40,9 +38,7 @@ dockers:
goos: linux
goarch: 386
goarm: ''
- dockerfile: dockerfile/i386/Dockerfile
- build_flag_templates:
- - "--label=com.centurylinklabs.watchtower=true"
+ dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:i386-{{ .Version }}
binaries:
@@ -51,9 +47,7 @@ dockers:
goos: linux
goarch: arm
goarm: 6
- dockerfile: dockerfile/armhf/Dockerfile
- build_flag_templates:
- - "--label=com.centurylinklabs.watchtower=true"
+ dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:armhf-{{ .Version }}
binaries:
@@ -62,9 +56,7 @@ dockers:
goos: linux
goarch: arm64
goarm: ''
- dockerfile: dockerfile/arm64v8/Dockerfile
- build_flag_templates:
- - "--label=com.centurylinklabs.watchtower=true"
+ dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:arm64v8-{{ .Version }}
binaries:
From 326223de0dc829a75cfaecb303a2971b9546a33c Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 18:24:54 +0200
Subject: [PATCH 090/761] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3d427fb..03d62ae 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,14 +1,19 @@
## Prerequisites
To contribute code changes to this project you will need the following development kits.
- * Go. [Download and install](https://golang.org/doc/install) the Go programming language
- * [docker](https://docs.docker.com/engine/installation/)
+ * [Go](https://golang.org/doc/install)
+ * [Docker](https://docs.docker.com/engine/installation/)
+
+As watchtower utilizes go modules for vendor locking, you'll need atleast Go 1.11.
+You can check your current version of the go language as follows:
+```bash
+ ~ $ go version
+ go version go1.12.1 darwin/amd64
+```
+
## Checking out the code
-When cloning watchtower to your development environment you should place your forked repo within the [standard go code structure](https://golang.org/doc/code.html#Organization).
+Do not place your code in the go source path.
```bash
-cd $GOPATH/src
-mkdir
-cd
git clone git@github.com:/watchtower.git
cd watchtower
```
@@ -16,9 +21,7 @@ cd watchtower
## Building and testing
watchtower is a go application and is built with go commands. The following commands assume that you are at the root level of your repo.
```bash
-go get -u github.com/Masterminds/glide # installs glide for vendoring
-glide install # retrieves package dependencies
go build # compiles and packages an executable binary, watchtower
-go test # runs tests
+go test ./... -v # runs tests with verbose output
./watchtower # runs the application (outside of a container)
```
From d10e8d2bf87be7491a632d1ca05441522890a36b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 21:08:11 +0200
Subject: [PATCH 091/761] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 03d62ae..db50329 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,3 +25,4 @@ go build # compiles and packages an executable bin
go test ./... -v # runs tests with verbose output
./watchtower # runs the application (outside of a container)
```
+If you dont have it enabled, you'll either have to prefix each command with `GO111MODULE=on` or run `export GO111MODULE=on` before running the commands. [You can read more about modules here.](https://github.com/golang/go/wiki/Modules)
From 9dd80681c75d2a4c35b4403c00a552537077e67d Mon Sep 17 00:00:00 2001
From: thelamer
Date: Sat, 13 Apr 2019 23:35:55 -0700
Subject: [PATCH 092/761] additional release logic to try to push manifested
releases on publish
---
.circleci/config.yml | 36 +++++++++++++++++++++++++++++++++++-
goreleaser.yml | 4 ++--
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index bead463..e95899d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -113,4 +113,38 @@ jobs:
echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin
- run:
name: Execute goreleaser
- command: CGO_ENABLED=${CGO_ENABLED:-0} ../goreleaser --debug
\ No newline at end of file
+ command: CGO_ENABLED=${CGO_ENABLED:-0} ../goreleaser --debug
+ - run:
+ name: Docker Manifest
+ command: |
+ mkdir -p ~/.docker/ && \
+ echo '{"experimental": "enabled"}' > ~/.docker/config.json && \
+ docker manifest create containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:amd64-${CIRCLE_TAG} containrrr/watchtower:i386-${CIRCLE_TAG} containrrr/watchtower:armhf-${CIRCLE_TAG} containrrr/watchtower:arm64v8-${CIRCLE_TAG} && \
+ docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:i386-${CIRCLE_TAG} --os linux --arch 386 && \
+ docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:armhf-${CIRCLE_TAG} --os linux --arch arm && \
+ docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:arm64v8-${CIRCLE_TAG} --os linux --arch arm64 --variant v8 && \
+ docker tag containrrr/watchtower:amd64-${CIRCLE_TAG} containrrr/watchtower:amd64-latest && \
+ docker tag containrrr/watchtower:i386-${CIRCLE_TAG} containrrr/watchtower:i386-latest && \
+ docker tag containrrr/watchtower:armhf-${CIRCLE_TAG} containrrr/watchtower:armhf-latest && \
+ docker tag containrrr/watchtower:arm64v8-${CIRCLE_TAG} containrrr/watchtower:arm64v8-latest && \
+ docker push containrrr/watchtower:amd64-latest && \
+ docker push containrrr/watchtower:i386-latest && \
+ docker push containrrr/watchtower:armhf-latest && \
+ docker push containrrr/watchtower:arm64v8-latest && \
+ docker manifest create containrrr/watchtower:latest containrrr/watchtower:amd64-latest containrrr/watchtower:i386-latest containrrr/watchtower:armhf-latest containrrr/watchtower:arm64v8-latest && \
+ docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:i386-latest --os linux --arch 386 && \
+ docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:armhf-latest --os linux --arch arm && \
+ docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:arm64v8-latest --os linux --arch arm64 --variant v8 && \
+ docker manifest push containrrr/watchtower:${CIRCLE_TAG} && \
+ docker manifest push containrrr/watchtower:latest
+ - run:
+ name: Sync Readme to Dockerhub
+ command: |
+ docker pull lsiodev/readme-sync && \
+ docker run --rm=true \
+ -e DOCKERHUB_USERNAME=$DOCKER_USER \
+ -e DOCKERHUB_PASSWORD="$DOCKER_PASS" \
+ -e GIT_REPOSITORY=containrrr/watchtower \
+ -e DOCKER_REPOSITORY=containrrr/watchtower \
+ -e GIT_BRANCH=master \
+ lsiodev/readme-sync bash -c 'node sync'
diff --git a/goreleaser.yml b/goreleaser.yml
index 2db606c..a14933c 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -31,7 +31,7 @@ dockers:
goarm: ''
dockerfile: Dockerfile
image_templates:
- - containrrr/watchtower:{{ .Version }}
+ - containrrr/watchtower:amd64-{{ .Version }}
binaries:
- watchtower
-
@@ -60,4 +60,4 @@ dockers:
image_templates:
- containrrr/watchtower:arm64v8-{{ .Version }}
binaries:
- - watchtower
\ No newline at end of file
+ - watchtower
From 09ddabd01c2bf7f464c4d69c715dd7394cc73d27 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 21:37:17 +0200
Subject: [PATCH 093/761] utilize goreleaser builtins and reformat code
---
.circleci/config.yml | 95 ++++++++++++++++++++++++++++++++++----------
goreleaser.yml | 4 ++
2 files changed, 79 insertions(+), 20 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e95899d..3069b51 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -37,10 +37,10 @@ workflows:
- testing
- linting
filters:
- tags:
- ignore: /^v[0-9]+(\.[0-9]+)*$/
branches:
only: /.*/
+ tags:
+ ignore: /^v[0-9]+(\.[0-9]+)*$/
- publishing:
requires:
- testing
@@ -115,26 +115,81 @@ jobs:
name: Execute goreleaser
command: CGO_ENABLED=${CGO_ENABLED:-0} ../goreleaser --debug
- run:
- name: Docker Manifest
+ name: Enable experimental docker features
command: |
mkdir -p ~/.docker/ && \
- echo '{"experimental": "enabled"}' > ~/.docker/config.json && \
- docker manifest create containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:amd64-${CIRCLE_TAG} containrrr/watchtower:i386-${CIRCLE_TAG} containrrr/watchtower:armhf-${CIRCLE_TAG} containrrr/watchtower:arm64v8-${CIRCLE_TAG} && \
- docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:i386-${CIRCLE_TAG} --os linux --arch 386 && \
- docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:armhf-${CIRCLE_TAG} --os linux --arch arm && \
- docker manifest annotate containrrr/watchtower:${CIRCLE_TAG} containrrr/watchtower:arm64v8-${CIRCLE_TAG} --os linux --arch arm64 --variant v8 && \
- docker tag containrrr/watchtower:amd64-${CIRCLE_TAG} containrrr/watchtower:amd64-latest && \
- docker tag containrrr/watchtower:i386-${CIRCLE_TAG} containrrr/watchtower:i386-latest && \
- docker tag containrrr/watchtower:armhf-${CIRCLE_TAG} containrrr/watchtower:armhf-latest && \
- docker tag containrrr/watchtower:arm64v8-${CIRCLE_TAG} containrrr/watchtower:arm64v8-latest && \
- docker push containrrr/watchtower:amd64-latest && \
- docker push containrrr/watchtower:i386-latest && \
- docker push containrrr/watchtower:armhf-latest && \
- docker push containrrr/watchtower:arm64v8-latest && \
- docker manifest create containrrr/watchtower:latest containrrr/watchtower:amd64-latest containrrr/watchtower:i386-latest containrrr/watchtower:armhf-latest containrrr/watchtower:arm64v8-latest && \
- docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:i386-latest --os linux --arch 386 && \
- docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:armhf-latest --os linux --arch arm && \
- docker manifest annotate containrrr/watchtower:latest containrrr/watchtower:arm64v8-latest --os linux --arch arm64 --variant v8 && \
+ echo '{"experimental": "enabled"}' > ~/.docker/config.json
+ - run:
+ name: Create manifest for version
+ command: |
+ docker manifest create \
+ containrrr/watchtower:${CIRCLE_TAG} \
+ containrrr/watchtower:amd64-${CIRCLE_TAG} \
+ containrrr/watchtower:i386-${CIRCLE_TAG} \
+ containrrr/watchtower:armhf-${CIRCLE_TAG} \
+ containrrr/watchtower:arm64v8-${CIRCLE_TAG}
+ - run:
+ name: Annotate i386 version
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:${CIRCLE_TAG} \
+ containrrr/watchtower:i386-${CIRCLE_TAG} \
+ --os linux \
+ --arch 386
+ - run:
+ name: Annotate ARM version
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:${CIRCLE_TAG} \
+ containrrr/watchtower:armhf-${CIRCLE_TAG} \
+ --os linux \
+ --arch arm
+ - run:
+ name: Annotate ARM64 version
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:${CIRCLE_TAG} \
+ containrrr/watchtower:arm64v8-${CIRCLE_TAG} \
+ --os linux \
+ --arch arm64 \
+ --variant v8
+ - run:
+ name: Create manifest for latest
+ command: |
+ docker manifest create \
+ containrrr/watchtower:latest \
+ containrrr/watchtower:amd64-latest \
+ containrrr/watchtower:i386-latest \
+ containrrr/watchtower:armhf-latest \
+ containrrr/watchtower:arm64v8-latest
+ - run:
+ name: Annotate i386 latest
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:latest \
+ containrrr/watchtower:i386-latest \
+ --os linux \
+ --arch 386
+ - run:
+ name: Annotate ARM latest
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:latest \
+ containrrr/watchtower:armhf-latest \
+ --os linux \
+ --arch arm
+ - run:
+ name: Annotate ARM64 latest
+ command: |
+ docker manifest annotate \
+ containrrr/watchtower:latest \
+ containrrr/watchtower:arm64v8-latest \
+ --os linux \
+ --arch arm64 \
+ --variant v8
+ - run:
+ name: Push manifests to Dockerhub
+ command: |
docker manifest push containrrr/watchtower:${CIRCLE_TAG} && \
docker manifest push containrrr/watchtower:latest
- run:
diff --git a/goreleaser.yml b/goreleaser.yml
index a14933c..4fa3841 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -32,6 +32,7 @@ dockers:
dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:amd64-{{ .Version }}
+ - containrrr/watchtower:amd64-latest
binaries:
- watchtower
-
@@ -41,6 +42,7 @@ dockers:
dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:i386-{{ .Version }}
+ - containrrr/watchtower:i386-latest
binaries:
- watchtower
-
@@ -50,6 +52,7 @@ dockers:
dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:armhf-{{ .Version }}
+ - containrrr/watchtower:armhf-latest
binaries:
- watchtower
-
@@ -59,5 +62,6 @@ dockers:
dockerfile: Dockerfile
image_templates:
- containrrr/watchtower:arm64v8-{{ .Version }}
+ - containrrr/watchtower:arm64v8-latest
binaries:
- watchtower
From b2e9282d08d8d79bfd83ac8cce893bb7d9c192d6 Mon Sep 17 00:00:00 2001
From: Ryan Kuba
Date: Sun, 14 Apr 2019 13:12:03 -0700
Subject: [PATCH 094/761] additional release logic to try to push manifested
releases on publish (#278)
From 93d47c7bd66ac0ace5996be06f0f758581338c01 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 22:32:14 +0200
Subject: [PATCH 095/761] strip v. from tag when creating manifests
---
.circleci/config.yml | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3069b51..155a3a4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -119,37 +119,41 @@ jobs:
command: |
mkdir -p ~/.docker/ && \
echo '{"experimental": "enabled"}' > ~/.docker/config.json
+ - run:
+ name: Strip letters from tag
+ command: |
+ export NUM_TAG=$(echo $CIRCLE_TAG | sed 's/^v*//')
- run:
name: Create manifest for version
command: |
docker manifest create \
- containrrr/watchtower:${CIRCLE_TAG} \
- containrrr/watchtower:amd64-${CIRCLE_TAG} \
- containrrr/watchtower:i386-${CIRCLE_TAG} \
- containrrr/watchtower:armhf-${CIRCLE_TAG} \
- containrrr/watchtower:arm64v8-${CIRCLE_TAG}
+ containrrr/watchtower:${NUM_TAG} \
+ containrrr/watchtower:amd64-${NUM_TAG} \
+ containrrr/watchtower:i386-${NUM_TAG} \
+ containrrr/watchtower:armhf-${NUM_TAG} \
+ containrrr/watchtower:arm64v8-${NUM_TAG}
- run:
name: Annotate i386 version
command: |
docker manifest annotate \
- containrrr/watchtower:${CIRCLE_TAG} \
- containrrr/watchtower:i386-${CIRCLE_TAG} \
+ containrrr/watchtower:${NUM_TAG} \
+ containrrr/watchtower:i386-${NUM_TAG} \
--os linux \
--arch 386
- run:
name: Annotate ARM version
command: |
docker manifest annotate \
- containrrr/watchtower:${CIRCLE_TAG} \
- containrrr/watchtower:armhf-${CIRCLE_TAG} \
+ containrrr/watchtower:${NUM_TAG} \
+ containrrr/watchtower:armhf-${NUM_TAG} \
--os linux \
--arch arm
- run:
name: Annotate ARM64 version
command: |
docker manifest annotate \
- containrrr/watchtower:${CIRCLE_TAG} \
- containrrr/watchtower:arm64v8-${CIRCLE_TAG} \
+ containrrr/watchtower:${NUM_TAG} \
+ containrrr/watchtower:arm64v8-${NUM_TAG} \
--os linux \
--arch arm64 \
--variant v8
@@ -190,7 +194,7 @@ jobs:
- run:
name: Push manifests to Dockerhub
command: |
- docker manifest push containrrr/watchtower:${CIRCLE_TAG} && \
+ docker manifest push containrrr/watchtower:${NUM_TAG} && \
docker manifest push containrrr/watchtower:latest
- run:
name: Sync Readme to Dockerhub
From 0e29029cb91386f5f13b3571b2f68ba38a3ce41d Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 22:50:23 +0200
Subject: [PATCH 096/761] fix tag splitting
---
.circleci/config.yml | 28 ++++++++++++----------------
1 file changed, 12 insertions(+), 16 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 155a3a4..3fe2d1a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -119,41 +119,37 @@ jobs:
command: |
mkdir -p ~/.docker/ && \
echo '{"experimental": "enabled"}' > ~/.docker/config.json
- - run:
- name: Strip letters from tag
- command: |
- export NUM_TAG=$(echo $CIRCLE_TAG | sed 's/^v*//')
- run:
name: Create manifest for version
command: |
docker manifest create \
- containrrr/watchtower:${NUM_TAG} \
- containrrr/watchtower:amd64-${NUM_TAG} \
- containrrr/watchtower:i386-${NUM_TAG} \
- containrrr/watchtower:armhf-${NUM_TAG} \
- containrrr/watchtower:arm64v8-${NUM_TAG}
+ containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:amd64-$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:i386-$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:armhf-$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:arm64v8-$(echo $CIRCLE_TAG | sed 's/^v*//')
- run:
name: Annotate i386 version
command: |
docker manifest annotate \
- containrrr/watchtower:${NUM_TAG} \
- containrrr/watchtower:i386-${NUM_TAG} \
+ containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:i386-$(echo $CIRCLE_TAG | sed 's/^v*//') \
--os linux \
--arch 386
- run:
name: Annotate ARM version
command: |
docker manifest annotate \
- containrrr/watchtower:${NUM_TAG} \
- containrrr/watchtower:armhf-${NUM_TAG} \
+ containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:armhf-$(echo $CIRCLE_TAG | sed 's/^v*//') \
--os linux \
--arch arm
- run:
name: Annotate ARM64 version
command: |
docker manifest annotate \
- containrrr/watchtower:${NUM_TAG} \
- containrrr/watchtower:arm64v8-${NUM_TAG} \
+ containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') \
+ containrrr/watchtower:arm64v8-$(echo $CIRCLE_TAG | sed 's/^v*//') \
--os linux \
--arch arm64 \
--variant v8
@@ -194,7 +190,7 @@ jobs:
- run:
name: Push manifests to Dockerhub
command: |
- docker manifest push containrrr/watchtower:${NUM_TAG} && \
+ docker manifest push containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') && \
docker manifest push containrrr/watchtower:latest
- run:
name: Sync Readme to Dockerhub
From 10031052b29e18cb91851c916771aeece1fdbd32 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 23:16:07 +0200
Subject: [PATCH 097/761] add additional login due to manifest shenanigans
---
.circleci/config.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3fe2d1a..7865950 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -190,6 +190,7 @@ jobs:
- run:
name: Push manifests to Dockerhub
command: |
+ echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin &&
docker manifest push containrrr/watchtower:$(echo $CIRCLE_TAG | sed 's/^v*//') && \
docker manifest push containrrr/watchtower:latest
- run:
From fc739c5ca89decff70bbccf47389ee6ec8a8adee Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 14 Apr 2019 23:25:08 +0200
Subject: [PATCH 098/761] switch from hound to codacy
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 73474f6..f183d2e 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,8 @@
-
-
+
+
From ba5ac33e93981985898fc083e7944ecba06c2d8e Mon Sep 17 00:00:00 2001
From: Matti Ranta
Date: Mon, 15 Apr 2019 11:32:56 -0400
Subject: [PATCH 099/761] check if schedule len > 0 to prevent collisions
---
main.go | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/main.go b/main.go
index 6936342..480b472 100644
--- a/main.go
+++ b/main.go
@@ -66,10 +66,11 @@ func before(c *cli.Context) error {
pollingSet := c.IsSet("interval")
cronSet := c.IsSet("schedule")
+ cronLen := len(c.String("schedule"))
- if pollingSet && cronSet {
+ if pollingSet && cronSet && cronLen > 0 {
log.Fatal("Only schedule or interval can be defined, not both.")
- } else if cronSet {
+ } else if cronSet && cronLen > 0 {
scheduleSpec = c.String("schedule")
} else {
scheduleSpec = "@every " + strconv.Itoa(c.Int("interval")) + "s"
From 3b0aafa7ff00d8d0b399dd632830513dc18c5ba8 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 15 Apr 2019 21:46:50 +0200
Subject: [PATCH 100/761] Delete .hound.yml
---
.hound.yml | 2 --
1 file changed, 2 deletions(-)
delete mode 100644 .hound.yml
diff --git a/.hound.yml b/.hound.yml
deleted file mode 100644
index e5c719d..0000000
--- a/.hound.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-go:
- enabled: true
From 86a5445f863797dd852131904edf0cb6916d3613 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 15 Apr 2019 22:21:32 +0200
Subject: [PATCH 101/761] Update issue templates
---
.github/ISSUE_TEMPLATE/bug_report.md | 2 +-
.github/ISSUE_TEMPLATE/feature_request.md | 2 +-
.github/ISSUE_TEMPLATE/question.md | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index a4a8eb6..76f46f4 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
-labels: bug
+labels: 'Priority: Medium, Status: Available, Type: Bug'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 11fc491..ca13f1d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
-labels: enhancement
+labels: 'Priority: Low, Status: Available, Type: Enhancement'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 9fa20c6..bce7228 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -1,8 +1,8 @@
---
name: Question
-about: Questions regarding usage or specific use cases.
+about: Questions regarding usage or specific use cases.
title: ''
-labels: question
+labels: 'Priority: Medium, Status: Available, Type: Question'
assignees: ''
---
From 3c38437b9341128685222d35f62e65f49252bc43 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 16 Apr 2019 10:00:30 +0200
Subject: [PATCH 102/761] send coverage to codacy
---
.circleci/config.yml | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7865950..8dff539 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -72,12 +72,11 @@ jobs:
steps:
- attach_workspace:
at: .
- - run: mkdir -p /tmp/test-results
- run: go build ./...
- - run: go get gotest.tools/gotestsum
- - run: gotestsum --junitfile /tmp/test-results/unit-tests.xml
- - store_test_results:
- path: /tmp/test-results
+ - run: go get github.com/schrej/godacov
+ - run: go get -u github.com/haya14busa/goverage
+ - run: goverage -v -coverprofile=coverage.out ./...
+ - run: godacov -t $CODACY_TOKEN -r ./coverage.out -c $CIRCLE_SHA1
build:
executor: go
steps:
From cdb0123ae2d22a2647053828e2300350ae15c9b6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 16 Apr 2019 10:31:51 +0200
Subject: [PATCH 103/761] add test coverage badge
---
README.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f183d2e..9c6f7b0 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,10 @@
-
+
+
+
+
From ccd746e9cc53f78f3c3ca5edb79df42fe4366855 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 16 Apr 2019 18:06:37 +0200
Subject: [PATCH 104/761] 281: fix mail notification regression
---
main.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/main.go b/main.go
index 480b472..d9042de 100644
--- a/main.go
+++ b/main.go
@@ -169,6 +169,7 @@ func runUpdatesWithNotifications(filter container.Filter) {
if err != nil {
log.Println(err)
}
+ notifier.SendNotification()
}
func setEnvOptStr(env string, opt string) error {
From 16b66ac10bfcf70497db38b9c9b63feabb728fc6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 12:46:35 +0200
Subject: [PATCH 105/761] pin down dockerfile alpine version
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index d3a324a..75f70f2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:latest as alpine
+FROM alpine:3.9 as alpine
RUN apk add --no-cache \
ca-certificates \
From f8dd9bb02a30944e75cb29d61b8ee3f6aed66db7 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 15:36:35 +0200
Subject: [PATCH 106/761] improve test coverage
---
container/client.go | 2 +-
container/container_test.go | 156 ++++++++++++++++++++++++++++++++----
container/trust_test.go | 5 +-
container/util_test.go | 3 +-
4 files changed, 148 insertions(+), 18 deletions(-)
diff --git a/container/client.go b/container/client.go
index 62bce6c..9afda01 100644
--- a/container/client.go
+++ b/container/client.go
@@ -34,7 +34,7 @@ type Client interface {
// * DOCKER_TLS_VERIFY whether to verify tls certificates
// * DOCKER_API_VERSION the minimum docker api version to work with
func NewClient(pullImages bool) Client {
- cli, err := dockerclient.NewEnvClient()
+ cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
diff --git a/container/container_test.go b/container/container_test.go
index e3a7f94..939ced5 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -1,25 +1,151 @@
package container
import (
- "github.com/stretchr/testify/assert"
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/container"
"testing"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
)
-func TestContainsWatchtowerLabel_ShouldReturnTrueIfTheWatchtowerLabelExistsOnTheContainer(t *testing.T) {
- labelMap := map[string]string {
- "com.centurylinklabs.watchtower": "true",
- }
- assert.True(t, ContainsWatchtowerLabel(labelMap))
+func TestContainer(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Container Suite")
}
-func TestContainsWatchtowerLabel_ShouldReturnFalseIfTheWatchtowerLabelDoesntExistOnTheContainer(t *testing.T) {
- labelMap := map[string]string {
- "com.containrrr.watchtower": "lost",
- }
- assert.False(t, ContainsWatchtowerLabel(labelMap))
+var _ = Describe("the container", func() {
+
+ When("asked for metadata", func() {
+ var c *Container
+ BeforeEach(func() {
+ c = mockContainerWithLabels(map[string]string {
+ "com.centurylinklabs.watchtower.enable": "true",
+ "com.centurylinklabs.watchtower": "true",
+ })
+ })
+ It("should return its name on calls to .Name()", func() {
+ name := c.Name()
+ Expect(name).To(Equal("test-containrrr"))
+ Expect(name).NotTo(Equal("wrong-name"))
+ })
+ It("should return its ID on calls to .ID()", func() {
+ id := c.ID()
+
+ Expect(id).To(Equal("container_id"))
+ Expect(id).NotTo(Equal("wrong-id"))
+ })
+ It("should return true, true if enabled on calls to .Enabled()", func() {
+ enabled, exists := c.Enabled()
+
+ Expect(enabled).To(BeTrue())
+ Expect(enabled).NotTo(BeFalse())
+ Expect(exists).To(BeTrue())
+ Expect(exists).NotTo(BeFalse())
+ })
+ It("should return false, true if present but not true on calls to .Enabled()", func() {
+ c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower.enable": "false" })
+ enabled, exists := c.Enabled()
+
+ Expect(enabled).To(BeFalse())
+ Expect(enabled).NotTo(BeTrue())
+ Expect(exists).To(BeTrue())
+ Expect(exists).NotTo(BeFalse())
+ })
+ It("should return false, false if not present on calls to .Enabled()", func() {
+ c = mockContainerWithLabels(map[string]string{ "lol": "false" })
+ enabled, exists := c.Enabled()
+
+ Expect(enabled).To(BeFalse())
+ Expect(enabled).NotTo(BeTrue())
+ Expect(exists).To(BeFalse())
+ Expect(exists).NotTo(BeTrue())
+ })
+ It("should return false, false if present but not parsable .Enabled()", func() {
+ c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower.enable": "falsy" })
+ enabled, exists := c.Enabled()
+
+ Expect(enabled).To(BeFalse())
+ Expect(enabled).NotTo(BeTrue())
+ Expect(exists).To(BeFalse())
+ Expect(exists).NotTo(BeTrue())
+ })
+ When("checking if its a watchtower instance", func() {
+ It("should return true if the label is set to true", func() {
+ isWatchtower := c.IsWatchtower()
+ Expect(isWatchtower).To(BeTrue())
+ })
+ It("should return false if the label is present but set to false", func() {
+ c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower": "false" })
+ isWatchtower := c.IsWatchtower()
+ Expect(isWatchtower).To(BeFalse())
+ })
+ It("should return false if the label is not present", func() {
+ c = mockContainerWithLabels(map[string]string{ "funny.label": "false" })
+ isWatchtower := c.IsWatchtower()
+ Expect(isWatchtower).To(BeFalse())
+ })
+ It("should return false if there are no labels", func() {
+ c = mockContainerWithLabels(map[string]string{})
+ isWatchtower := c.IsWatchtower()
+ Expect(isWatchtower).To(BeFalse())
+ })
+ })
+ When("fetching the custom stop signal", func() {
+ It("should return the signal if its set", func() {
+ c = mockContainerWithLabels(map[string]string{
+ "com.centurylinklabs.watchtower.stop-signal": "SIGKILL",
+ })
+ stopSignal := c.StopSignal()
+ Expect(stopSignal).To(Equal("SIGKILL"))
+ })
+ It("should return an empty string if its not set", func() {
+ c = mockContainerWithLabels(map[string]string{})
+ stopSignal := c.StopSignal()
+ Expect(stopSignal).To(Equal(""))
+ })
+ })
+ When("fetching the image name", func() {
+ When("the zodiac label is present", func() {
+ It("should fetch the image name from it", func() {
+ c = mockContainerWithLabels(map[string]string{
+ "com.centurylinklabs.zodiac.original-image": "the-original-image",
+ })
+ imageName := c.ImageName()
+ Expect(imageName).To(Equal(imageName))
+ })
+ })
+ It("should return the image name", func() {
+ name := "image-name:3"
+ c = mockContainerWithImageName(name)
+ imageName := c.ImageName()
+ Expect(imageName).To(Equal(name))
+ })
+ It("should assume latest if no tag is supplied", func() {
+ name := "image-name"
+ c = mockContainerWithImageName(name)
+ imageName := c.ImageName()
+ Expect(imageName).To(Equal(name + ":latest"))
+ })
+ })
+ })
+})
+
+func mockContainerWithImageName(name string) *Container {
+ container := mockContainerWithLabels(nil)
+ container.containerInfo.Config.Image = name
+ return container
}
-func TestContainsWatchtowerLabel_ShouldReturnFalseIfLabelsIsEmpty(t *testing.T) {
- labelMap := map[string]string {}
- assert.False(t, ContainsWatchtowerLabel(labelMap))
-}
+func mockContainerWithLabels(labels map[string]string) *Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: "container_id",
+ Image: "image",
+ Name: "test-containrrr",
+ },
+ Config: &container.Config{
+ Labels: labels,
+ },
+ }
+ return NewContainer(&content, nil)
+}
\ No newline at end of file
diff --git a/container/trust_test.go b/container/trust_test.go
index 481ce64..6aa807b 100644
--- a/container/trust_test.go
+++ b/container/trust_test.go
@@ -3,10 +3,13 @@ package container
import (
"os"
"testing"
-
"github.com/stretchr/testify/assert"
)
+
+
+
+
func TestEncodedEnvAuth_ShouldReturnAnErrorIfRepoEnvsAreUnset(t *testing.T) {
os.Unsetenv("REPO_USER")
os.Unsetenv("REPO_PASS")
diff --git a/container/util_test.go b/container/util_test.go
index c4d5ca7..8c4eef9 100644
--- a/container/util_test.go
+++ b/container/util_test.go
@@ -2,10 +2,11 @@ package container
import (
"testing"
-
"github.com/stretchr/testify/assert"
)
+
+
func TestSliceEqual_True(t *testing.T) {
s1 := []string{"a", "b", "c"}
s2 := []string{"a", "b", "c"}
From 3a63b5556f1ca79f0479a0ec6a31eec35a227284 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 15:50:18 +0200
Subject: [PATCH 107/761] Add placeholder client test
---
container/client_test.go | 15 +++++++++++++++
1 file changed, 15 insertions(+)
create mode 100644 container/client_test.go
diff --git a/container/client_test.go b/container/client_test.go
new file mode 100644
index 0000000..ab3df0d
--- /dev/null
+++ b/container/client_test.go
@@ -0,0 +1,15 @@
+package container
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("the client", func() {
+ When("creating a new client", func() {
+ It("should return a client for the api", func() {
+ client := NewClient(false)
+ Expect(client).NotTo(BeNil())
+ })
+ })
+})
\ No newline at end of file
From e2228f1b0b6bc078a1dc4662f1e08fb515a76a6a Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 21:28:48 +0200
Subject: [PATCH 108/761] improve test coverage and add an api-server mock
---
container/client.go | 2 +-
container/client_test.go | 15 --
container/container_test.go | 47 +++-
container/mocks/ApiServer.go | 49 ++++
container/mocks/data/container_running.json | 233 ++++++++++++++++++++
container/mocks/data/container_stopped.json | 205 +++++++++++++++++
container/mocks/data/containers.json | 113 ++++++++++
container/mocks/data/image01.json | 99 +++++++++
container/mocks/data/image02.json | 92 ++++++++
9 files changed, 836 insertions(+), 19 deletions(-)
delete mode 100644 container/client_test.go
create mode 100644 container/mocks/ApiServer.go
create mode 100644 container/mocks/data/container_running.json
create mode 100644 container/mocks/data/container_stopped.json
create mode 100644 container/mocks/data/containers.json
create mode 100644 container/mocks/data/image01.json
create mode 100644 container/mocks/data/image02.json
diff --git a/container/client.go b/container/client.go
index 9afda01..94f790e 100644
--- a/container/client.go
+++ b/container/client.go
@@ -44,7 +44,7 @@ func NewClient(pullImages bool) Client {
}
type dockerClient struct {
- api *dockerclient.Client
+ api dockerclient.CommonAPIClient
pullImages bool
}
diff --git a/container/client_test.go b/container/client_test.go
deleted file mode 100644
index ab3df0d..0000000
--- a/container/client_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package container
-
-import (
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("the client", func() {
- When("creating a new client", func() {
- It("should return a client for the api", func() {
- client := NewClient(false)
- Expect(client).NotTo(BeNil())
- })
- })
-})
\ No newline at end of file
diff --git a/container/container_test.go b/container/container_test.go
index 939ced5..47ec8e0 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -1,11 +1,13 @@
package container
import (
+ "github.com/containrrr/watchtower/container/mocks"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
- "testing"
+ cli "github.com/docker/docker/client"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ "testing"
)
func TestContainer(t *testing.T) {
@@ -14,7 +16,46 @@ func TestContainer(t *testing.T) {
}
var _ = Describe("the container", func() {
-
+ Describe("the client", func() {
+ var client Client
+ BeforeSuite(func() {
+ server := mocks.NewMockApiServer()
+ c, _ := cli.NewClientWithOpts(
+ cli.WithHost(server.URL),
+ cli.WithHTTPClient(server.Client(),
+ ))
+ client = dockerClient{
+ api: c,
+ pullImages: false,
+ }
+ })
+ It("should return a client for the api", func() {
+ Expect(client).NotTo(BeNil())
+ })
+ When("listing containers without any filter", func() {
+ It("should return all available containers", func() {
+ containers, err := client.ListContainers(noFilter)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(len(containers) == 2).To(BeTrue())
+ })
+ })
+ When("listing containers with a filter matching nothing", func() {
+ It("should return an empty array", func() {
+ filter := filterByNames([]string { "lollercoaster"}, noFilter)
+ containers, err := client.ListContainers(filter)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(len(containers) == 0).To(BeTrue())
+ })
+ })
+ When("listing containers with a watchtower filter", func() {
+ It("should return only the watchtower container", func() {
+ containers, err := client.ListContainers(WatchtowerContainersFilter)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(len(containers) == 1).To(BeTrue())
+ Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
+ })
+ })
+ })
When("asked for metadata", func() {
var c *Container
BeforeEach(func() {
@@ -148,4 +189,4 @@ func mockContainerWithLabels(labels map[string]string) *Container {
},
}
return NewContainer(&content, nil)
-}
\ No newline at end of file
+}
diff --git a/container/mocks/ApiServer.go b/container/mocks/ApiServer.go
new file mode 100644
index 0000000..f63b93b
--- /dev/null
+++ b/container/mocks/ApiServer.go
@@ -0,0 +1,49 @@
+package mocks
+
+import (
+ "fmt"
+ "github.com/sirupsen/logrus"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "path/filepath"
+ "strings"
+)
+
+func NewMockApiServer() *httptest.Server {
+ return httptest.NewServer(http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ logrus.Debug("Mock server has received a HTTP call on ", r.URL)
+ var response = ""
+
+ 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")
+ } else if isRequestFor("b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", r) {
+ response = getMockJsonFromDisk("./mocks/data/container_running.json")
+ } else if isRequestFor("sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd", r) {
+ response = getMockJsonFromDisk("./mocks/data/image01.json")
+ } else if isRequestFor("sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", r) {
+ response = getMockJsonFromDisk("./mocks/data/image02.json")
+ }
+ fmt.Fprintln(w, response)
+ },
+ ))
+}
+
+func isRequestFor(urlPart string, r *http.Request) bool {
+ return strings.Contains(r.URL.String(), urlPart)
+}
+
+func getMockJsonFromDisk(relPath string) string {
+ absPath, _ := filepath.Abs(relPath)
+ logrus.Error(absPath)
+ buf, err := ioutil.ReadFile(absPath)
+ if err != nil {
+ logrus.Error(err)
+ return ""
+ }
+ return string(buf)
+}
+
diff --git a/container/mocks/data/container_running.json b/container/mocks/data/container_running.json
new file mode 100644
index 0000000..2d78bb3
--- /dev/null
+++ b/container/mocks/data/container_running.json
@@ -0,0 +1,233 @@
+{
+ "Id": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
+ "Created": "2019-04-04T20:28:32.5710901Z",
+ "Path": "/portainer",
+ "Args": [],
+ "State": {
+ "Status": "running",
+ "Running": true,
+ "Paused": false,
+ "Restarting": false,
+ "OOMKilled": false,
+ "Dead": false,
+ "Pid": 3854,
+ "ExitCode": 0,
+ "Error": "",
+ "StartedAt": "2019-04-13T22:38:24.498745809Z",
+ "FinishedAt": "2019-04-13T22:38:18.486292076Z"
+ },
+ "Image": "sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
+ "ResolvConfPath": "/var/lib/docker/containers/b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008/resolv.conf",
+ "HostnamePath": "/var/lib/docker/containers/b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008/hostname",
+ "HostsPath": "/var/lib/docker/containers/b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008/hosts",
+ "LogPath": "/var/lib/docker/containers/b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008/b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008-json.log",
+ "Name": "/portainer",
+ "RestartCount": 0,
+ "Driver": "overlay2",
+ "Platform": "linux",
+ "MountLabel": "",
+ "ProcessLabel": "",
+ "AppArmorProfile": "",
+ "ExecIDs": null,
+ "HostConfig": {
+ "Binds": [
+ "portainer_data:/data",
+ "/var/run/docker.sock:/var/run/docker.sock"
+ ],
+ "ContainerIDFile": "",
+ "LogConfig": {
+ "Type": "json-file",
+ "Config": {}
+ },
+ "NetworkMode": "default",
+ "PortBindings": {
+ "9000/tcp": [
+ {
+ "HostIp": "",
+ "HostPort": "9000"
+ }
+ ]
+ },
+ "RestartPolicy": {
+ "Name": "always",
+ "MaximumRetryCount": 0
+ },
+ "AutoRemove": false,
+ "VolumeDriver": "",
+ "VolumesFrom": null,
+ "CapAdd": null,
+ "CapDrop": null,
+ "Dns": [],
+ "DnsOptions": [],
+ "DnsSearch": [],
+ "ExtraHosts": null,
+ "GroupAdd": null,
+ "IpcMode": "shareable",
+ "Cgroup": "",
+ "Links": null,
+ "OomScoreAdj": 0,
+ "PidMode": "",
+ "Privileged": false,
+ "PublishAllPorts": false,
+ "ReadonlyRootfs": false,
+ "SecurityOpt": null,
+ "UTSMode": "",
+ "UsernsMode": "",
+ "ShmSize": 67108864,
+ "Runtime": "runc",
+ "ConsoleSize": [
+ 0,
+ 0
+ ],
+ "Isolation": "",
+ "CpuShares": 0,
+ "Memory": 0,
+ "NanoCpus": 0,
+ "CgroupParent": "",
+ "BlkioWeight": 0,
+ "BlkioWeightDevice": [],
+ "BlkioDeviceReadBps": null,
+ "BlkioDeviceWriteBps": null,
+ "BlkioDeviceReadIOps": null,
+ "BlkioDeviceWriteIOps": null,
+ "CpuPeriod": 0,
+ "CpuQuota": 0,
+ "CpuRealtimePeriod": 0,
+ "CpuRealtimeRuntime": 0,
+ "CpusetCpus": "",
+ "CpusetMems": "",
+ "Devices": [],
+ "DeviceCgroupRules": null,
+ "DiskQuota": 0,
+ "KernelMemory": 0,
+ "MemoryReservation": 0,
+ "MemorySwap": 0,
+ "MemorySwappiness": null,
+ "OomKillDisable": false,
+ "PidsLimit": 0,
+ "Ulimits": null,
+ "CpuCount": 0,
+ "CpuPercent": 0,
+ "IOMaximumIOps": 0,
+ "IOMaximumBandwidth": 0,
+ "MaskedPaths": [
+ "/proc/asound",
+ "/proc/acpi",
+ "/proc/kcore",
+ "/proc/keys",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ "/proc/scsi",
+ "/sys/firmware"
+ ],
+ "ReadonlyPaths": [
+ "/proc/bus",
+ "/proc/fs",
+ "/proc/irq",
+ "/proc/sys",
+ "/proc/sysrq-trigger"
+ ]
+ },
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/docker/overlay2/99dedacb757cd8c70ccacbc4b57dd85cb34b1b6fcfd2fd1176332ce5dfa1d38c-init/diff:/var/lib/docker/overlay2/2e0c03c2476f5b4df855cb8b02a88f76d336d7e0becc3e5193906aaa760687fd/diff:/var/lib/docker/overlay2/6c3f44131f6f13c9ea1a99a1b24bf348f70ba3eef244f29202faef3a2216ac11/diff",
+ "MergedDir": "/var/lib/docker/overlay2/99dedacb757cd8c70ccacbc4b57dd85cb34b1b6fcfd2fd1176332ce5dfa1d38c/merged",
+ "UpperDir": "/var/lib/docker/overlay2/99dedacb757cd8c70ccacbc4b57dd85cb34b1b6fcfd2fd1176332ce5dfa1d38c/diff",
+ "WorkDir": "/var/lib/docker/overlay2/99dedacb757cd8c70ccacbc4b57dd85cb34b1b6fcfd2fd1176332ce5dfa1d38c/work"
+ },
+ "Name": "overlay2"
+ },
+ "Mounts": [
+ {
+ "Type": "volume",
+ "Name": "portainer_data",
+ "Source": "/var/lib/docker/volumes/portainer_data/_data",
+ "Destination": "/data",
+ "Driver": "local",
+ "Mode": "z",
+ "RW": true,
+ "Propagation": ""
+ },
+ {
+ "Type": "bind",
+ "Source": "/var/run/docker.sock",
+ "Destination": "/var/run/docker.sock",
+ "Mode": "",
+ "RW": true,
+ "Propagation": "rprivate"
+ }
+ ],
+ "Config": {
+ "Hostname": "822f0f2efd78",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "ExposedPorts": {
+ "9000/tcp": {}
+ },
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": null,
+ "Image": "portainer/portainer:latest",
+ "Volumes": {
+ "/data": {}
+ },
+ "WorkingDir": "/",
+ "Entrypoint": [
+ "/portainer"
+ ],
+ "OnBuild": null,
+ "Labels": {}
+ },
+ "NetworkSettings": {
+ "Bridge": "",
+ "SandboxID": "8819e19588be798020f2d09e36a577c39a47809e68c2769a1525880c0bcd5b11",
+ "HairpinMode": false,
+ "LinkLocalIPv6Address": "",
+ "LinkLocalIPv6PrefixLen": 0,
+ "Ports": {
+ "9000/tcp": [
+ {
+ "HostIp": "0.0.0.0",
+ "HostPort": "9000"
+ }
+ ]
+ },
+ "SandboxKey": "/var/run/docker/netns/8819e19588be",
+ "SecondaryIPAddresses": null,
+ "SecondaryIPv6Addresses": null,
+ "EndpointID": "a8bcd737f27edb4d2955f7bce0c777bb2990b792a6b335b0727387624abe0702",
+ "Gateway": "172.17.0.1",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "IPAddress": "172.17.0.2",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "MacAddress": "02:42:ac:11:00:02",
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "9352796e0330dcf31ce3d44fae4b719304b8b3fd97b02ade3aefb8737251682b",
+ "EndpointID": "a8bcd737f27edb4d2955f7bce0c777bb2990b792a6b335b0727387624abe0702",
+ "Gateway": "172.17.0.1",
+ "IPAddress": "172.17.0.2",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "02:42:ac:11:00:02",
+ "DriverOpts": null
+ }
+ }
+ }
+}
diff --git a/container/mocks/data/container_stopped.json b/container/mocks/data/container_stopped.json
new file mode 100644
index 0000000..a4519d7
--- /dev/null
+++ b/container/mocks/data/container_stopped.json
@@ -0,0 +1,205 @@
+{
+ "Id": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
+ "Created": "2019-04-10T19:51:22.245041005Z",
+ "Path": "/watchtower",
+ "Args": [],
+ "State": {
+ "Status": "exited",
+ "Running": false,
+ "Paused": false,
+ "Restarting": false,
+ "OOMKilled": false,
+ "Dead": false,
+ "Pid": 0,
+ "ExitCode": 1,
+ "Error": "",
+ "StartedAt": "2019-04-10T19:51:22.918972606Z",
+ "FinishedAt": "2019-04-10T19:52:14.265091583Z"
+ },
+ "Image": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa",
+ "ResolvConfPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/resolv.conf",
+ "HostnamePath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hostname",
+ "HostsPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hosts",
+ "LogPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65-json.log",
+ "Name": "/watchtower-test",
+ "RestartCount": 0,
+ "Driver": "overlay2",
+ "Platform": "linux",
+ "MountLabel": "",
+ "ProcessLabel": "",
+ "AppArmorProfile": "",
+ "ExecIDs": null,
+ "HostConfig": {
+ "Binds": [
+ "/var/run/docker.sock:/var/run/docker.sock"
+ ],
+ "ContainerIDFile": "",
+ "LogConfig": {
+ "Type": "json-file",
+ "Config": {}
+ },
+ "NetworkMode": "default",
+ "PortBindings": {},
+ "RestartPolicy": {
+ "Name": "no",
+ "MaximumRetryCount": 0
+ },
+ "AutoRemove": false,
+ "VolumeDriver": "",
+ "VolumesFrom": null,
+ "CapAdd": null,
+ "CapDrop": null,
+ "Dns": [],
+ "DnsOptions": [],
+ "DnsSearch": [],
+ "ExtraHosts": null,
+ "GroupAdd": null,
+ "IpcMode": "shareable",
+ "Cgroup": "",
+ "Links": null,
+ "OomScoreAdj": 0,
+ "PidMode": "",
+ "Privileged": false,
+ "PublishAllPorts": false,
+ "ReadonlyRootfs": false,
+ "SecurityOpt": null,
+ "UTSMode": "",
+ "UsernsMode": "",
+ "ShmSize": 67108864,
+ "Runtime": "runc",
+ "ConsoleSize": [
+ 0,
+ 0
+ ],
+ "Isolation": "",
+ "CpuShares": 0,
+ "Memory": 0,
+ "NanoCpus": 0,
+ "CgroupParent": "",
+ "BlkioWeight": 0,
+ "BlkioWeightDevice": [],
+ "BlkioDeviceReadBps": null,
+ "BlkioDeviceWriteBps": null,
+ "BlkioDeviceReadIOps": null,
+ "BlkioDeviceWriteIOps": null,
+ "CpuPeriod": 0,
+ "CpuQuota": 0,
+ "CpuRealtimePeriod": 0,
+ "CpuRealtimeRuntime": 0,
+ "CpusetCpus": "",
+ "CpusetMems": "",
+ "Devices": [],
+ "DeviceCgroupRules": null,
+ "DiskQuota": 0,
+ "KernelMemory": 0,
+ "MemoryReservation": 0,
+ "MemorySwap": 0,
+ "MemorySwappiness": null,
+ "OomKillDisable": false,
+ "PidsLimit": 0,
+ "Ulimits": null,
+ "CpuCount": 0,
+ "CpuPercent": 0,
+ "IOMaximumIOps": 0,
+ "IOMaximumBandwidth": 0,
+ "MaskedPaths": [
+ "/proc/asound",
+ "/proc/acpi",
+ "/proc/kcore",
+ "/proc/keys",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ "/proc/scsi",
+ "/sys/firmware"
+ ],
+ "ReadonlyPaths": [
+ "/proc/bus",
+ "/proc/fs",
+ "/proc/irq",
+ "/proc/sys",
+ "/proc/sysrq-trigger"
+ ]
+ },
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc-init/diff:/var/lib/docker/overlay2/cdf82f50bc49177d0c17c24f3eaa29eba607b70cc6a081f77781b21c59a13eb8/diff:/var/lib/docker/overlay2/8108325ee844603c9b08d2772cf6e65dccf31dd5171f265078e5ed79a0ba3c0f/diff:/var/lib/docker/overlay2/e5e0cce6bf91b829a308424d99d7e56a33be3a11414ff5cdc48e762a1342b20f/diff",
+ "MergedDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/merged",
+ "UpperDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/diff",
+ "WorkDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/work"
+ },
+ "Name": "overlay2"
+ },
+ "Mounts": [
+ {
+ "Type": "bind",
+ "Source": "/var/run/docker.sock",
+ "Destination": "/var/run/docker.sock",
+ "Mode": "",
+ "RW": true,
+ "Propagation": "rprivate"
+ }
+ ],
+ "Config": {
+ "Hostname": "ae8964ba86c7",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": null,
+ "Image": "containrrr/watchtower:latest",
+ "Volumes": null,
+ "WorkingDir": "",
+ "Entrypoint": [
+ "/watchtower"
+ ],
+ "OnBuild": null,
+ "Labels": {
+ "com.centurylinklabs.watchtower": "true"
+ }
+ },
+ "NetworkSettings": {
+ "Bridge": "",
+ "SandboxID": "05627d36c08ed994eebc44a2a8c9365a511756b55c500fb03fd5a14477cd4bf3",
+ "HairpinMode": false,
+ "LinkLocalIPv6Address": "",
+ "LinkLocalIPv6PrefixLen": 0,
+ "Ports": {},
+ "SandboxKey": "/var/run/docker/netns/05627d36c08e",
+ "SecondaryIPAddresses": null,
+ "SecondaryIPv6Addresses": null,
+ "EndpointID": "",
+ "Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "IPv6Gateway": "",
+ "MacAddress": "",
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "8fcfd56fa9203bafa98510abb08bff66ad05bef5b6e97d158cbae3397e1e065e",
+ "EndpointID": "",
+ "Gateway": "",
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "",
+ "DriverOpts": null
+ }
+ }
+ }
+}
diff --git a/container/mocks/data/containers.json b/container/mocks/data/containers.json
new file mode 100644
index 0000000..a40cbf3
--- /dev/null
+++ b/container/mocks/data/containers.json
@@ -0,0 +1,113 @@
+[
+ {
+ "Id": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
+ "Names": [
+ "/watchtower-test"
+ ],
+ "Image": "containrrr/watchtower:latest",
+ "ImageID": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa",
+ "Command": "/watchtower",
+ "Created": 1554925882,
+ "Ports": [],
+ "Labels": {
+ "com.centurylinklabs.watchtower": "true"
+ },
+ "State": "exited",
+ "Status": "Exited (1) 6 days ago",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "8fcfd56fa9203bafa98510abb08bff66ad05bef5b6e97d158cbae3397e1e065e",
+ "EndpointID": "",
+ "Gateway": "",
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "",
+ "DriverOpts": null
+ }
+ }
+ },
+ "Mounts": [
+ {
+ "Type": "bind",
+ "Source": "/var/run/docker.sock",
+ "Destination": "/var/run/docker.sock",
+ "Mode": "",
+ "RW": true,
+ "Propagation": "rprivate"
+ }
+ ]
+ },
+ {
+ "Id": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
+ "Names": [
+ "/portainer"
+ ],
+ "Image": "portainer/portainer:latest",
+ "ImageID": "sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
+ "Command": "/portainer",
+ "Created": 1554409712,
+ "Ports": [
+ {
+ "IP": "0.0.0.0",
+ "PrivatePort": 9000,
+ "PublicPort": 9000,
+ "Type": "tcp"
+ }
+ ],
+ "Labels": {},
+ "State": "running",
+ "Status": "Up 3 days",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "9352796e0330dcf31ce3d44fae4b719304b8b3fd97b02ade3aefb8737251682b",
+ "EndpointID": "a8bcd737f27edb4d2955f7bce0c777bb2990b792a6b335b0727387624abe0702",
+ "Gateway": "172.17.0.1",
+ "IPAddress": "172.17.0.2",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "02:42:ac:11:00:02",
+ "DriverOpts": null
+ }
+ }
+ },
+ "Mounts": [
+ {
+ "Type": "volume",
+ "Name": "portainer_data",
+ "Source": "/var/lib/docker/volumes/portainer_data/_data",
+ "Destination": "/data",
+ "Driver": "local",
+ "Mode": "z",
+ "RW": true,
+ "Propagation": ""
+ },
+ {
+ "Type": "bind",
+ "Source": "/var/run/docker.sock",
+ "Destination": "/var/run/docker.sock",
+ "Mode": "",
+ "RW": true,
+ "Propagation": "rprivate"
+ }
+ ]
+ }
+]
diff --git a/container/mocks/data/image01.json b/container/mocks/data/image01.json
new file mode 100644
index 0000000..685d087
--- /dev/null
+++ b/container/mocks/data/image01.json
@@ -0,0 +1,99 @@
+{
+ "Id": "sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
+ "RepoTags": [
+ "portainer/portainer:latest"
+ ],
+ "RepoDigests": [
+ "portainer/portainer@sha256:d6cc2c20c0af38d8d557ab994c419c799a10fe825e4aa57fea2e2e507a13747d"
+ ],
+ "Parent": "",
+ "Comment": "",
+ "Created": "2019-03-05T04:41:17.612066939Z",
+ "Container": "022100cf79dfee27867d5ff7aa3ff7ecc5cbd486747e808a59b6accd393d65f5",
+ "ContainerConfig": {
+ "Hostname": "022100cf79df",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "ExposedPorts": {
+ "9000/tcp": {}
+ },
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": [
+ "/bin/sh",
+ "-c",
+ "#(nop) ",
+ "ENTRYPOINT [\"/portainer\"]"
+ ],
+ "Image": "sha256:9cf3ead5068a16f1bc1e18d6e730940f05fd59f60dfe1f6b3a5956196191dc77",
+ "Volumes": {
+ "/data": {}
+ },
+ "WorkingDir": "/",
+ "Entrypoint": [
+ "/portainer"
+ ],
+ "OnBuild": null,
+ "Labels": {}
+ },
+ "DockerVersion": "18.09.2",
+ "Author": "",
+ "Config": {
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "ExposedPorts": {
+ "9000/tcp": {}
+ },
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": null,
+ "Image": "sha256:9cf3ead5068a16f1bc1e18d6e730940f05fd59f60dfe1f6b3a5956196191dc77",
+ "Volumes": {
+ "/data": {}
+ },
+ "WorkingDir": "/",
+ "Entrypoint": [
+ "/portainer"
+ ],
+ "OnBuild": null,
+ "Labels": null
+ },
+ "Architecture": "amd64",
+ "Os": "linux",
+ "Size": 74089106,
+ "VirtualSize": 74089106,
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/docker/overlay2/6c3f44131f6f13c9ea1a99a1b24bf348f70ba3eef244f29202faef3a2216ac11/diff",
+ "MergedDir": "/var/lib/docker/overlay2/2e0c03c2476f5b4df855cb8b02a88f76d336d7e0becc3e5193906aaa760687fd/merged",
+ "UpperDir": "/var/lib/docker/overlay2/2e0c03c2476f5b4df855cb8b02a88f76d336d7e0becc3e5193906aaa760687fd/diff",
+ "WorkDir": "/var/lib/docker/overlay2/2e0c03c2476f5b4df855cb8b02a88f76d336d7e0becc3e5193906aaa760687fd/work"
+ },
+ "Name": "overlay2"
+ },
+ "RootFS": {
+ "Type": "layers",
+ "Layers": [
+ "sha256:dd4969f97241b9aefe2a70f560ce399ee9fa0354301c9aef841082ad52161ec5",
+ "sha256:e7260fd2a5f240122129b2d421726d7a4a2bda0cc292e962b694196af8856f20"
+ ]
+ },
+ "Metadata": {
+ "LastTagTime": "0001-01-01T00:00:00Z"
+ }
+}
diff --git a/container/mocks/data/image02.json b/container/mocks/data/image02.json
new file mode 100644
index 0000000..64850ba
--- /dev/null
+++ b/container/mocks/data/image02.json
@@ -0,0 +1,92 @@
+{
+ "Id": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa",
+ "RepoTags": [
+ "containrrr/watchtower:latest"
+ ],
+ "RepoDigests": [],
+ "Parent": "sha256:2753b9621e0d76153e1725d0cea015baf0ae4d829782a463b4ea9532ec976447",
+ "Comment": "",
+ "Created": "2019-04-10T19:49:07.970840451Z",
+ "Container": "b8387976426946f5c5191255204a66514c5e64be157f792c5bac329bb055041c",
+ "ContainerConfig": {
+ "Hostname": "b83879764269",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": [
+ "/bin/sh",
+ "-c",
+ "#(nop) ",
+ "ENTRYPOINT [\"/watchtower\"]"
+ ],
+ "Image": "sha256:2753b9621e0d76153e1725d0cea015baf0ae4d829782a463b4ea9532ec976447",
+ "Volumes": null,
+ "WorkingDir": "",
+ "Entrypoint": [
+ "/watchtower"
+ ],
+ "OnBuild": null,
+ "Labels": {
+ "com.centurylinklabs.watchtower": "true"
+ }
+ },
+ "DockerVersion": "18.09.1",
+ "Author": "",
+ "Config": {
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "Cmd": null,
+ "Image": "sha256:2753b9621e0d76153e1725d0cea015baf0ae4d829782a463b4ea9532ec976447",
+ "Volumes": null,
+ "WorkingDir": "",
+ "Entrypoint": [
+ "/watchtower"
+ ],
+ "OnBuild": null,
+ "Labels": {
+ "com.centurylinklabs.watchtower": "true"
+ }
+ },
+ "Architecture": "amd64",
+ "Os": "linux",
+ "Size": 13005733,
+ "VirtualSize": 13005733,
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/docker/overlay2/8108325ee844603c9b08d2772cf6e65dccf31dd5171f265078e5ed79a0ba3c0f/diff:/var/lib/docker/overlay2/e5e0cce6bf91b829a308424d99d7e56a33be3a11414ff5cdc48e762a1342b20f/diff",
+ "MergedDir": "/var/lib/docker/overlay2/cdf82f50bc49177d0c17c24f3eaa29eba607b70cc6a081f77781b21c59a13eb8/merged",
+ "UpperDir": "/var/lib/docker/overlay2/cdf82f50bc49177d0c17c24f3eaa29eba607b70cc6a081f77781b21c59a13eb8/diff",
+ "WorkDir": "/var/lib/docker/overlay2/cdf82f50bc49177d0c17c24f3eaa29eba607b70cc6a081f77781b21c59a13eb8/work"
+ },
+ "Name": "overlay2"
+ },
+ "RootFS": {
+ "Type": "layers",
+ "Layers": [
+ "sha256:1d3ad125af2c636cdd793fcf94c9d4fd2b5c4c7d63a770a01056719db13c2271",
+ "sha256:06cfe8fe0892ba4a91cb93e3a25344d4a1c4771cf7297a93e3bd86a1e0fba6eb",
+ "sha256:f58d451769dc30a938d8dcae22fda2acd816899f65fc6b6fa519ddf230dab447"
+ ]
+ },
+ "Metadata": {
+ "LastTagTime": "2019-04-10T19:49:08.03921105Z"
+ }
+}
From 62a95f0e48459429e18f17a54b93e05101598be8 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 21:32:09 +0200
Subject: [PATCH 109/761] Fix linting errors
---
container/container_test.go | 2 +-
container/mocks/ApiServer.go | 16 +++++++++-------
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/container/container_test.go b/container/container_test.go
index 47ec8e0..2543976 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -19,7 +19,7 @@ var _ = Describe("the container", func() {
Describe("the client", func() {
var client Client
BeforeSuite(func() {
- server := mocks.NewMockApiServer()
+ server := mocks.NewMockAPIServer()
c, _ := cli.NewClientWithOpts(
cli.WithHost(server.URL),
cli.WithHTTPClient(server.Client(),
diff --git a/container/mocks/ApiServer.go b/container/mocks/ApiServer.go
index f63b93b..79290a2 100644
--- a/container/mocks/ApiServer.go
+++ b/container/mocks/ApiServer.go
@@ -10,22 +10,24 @@ import (
"strings"
)
-func NewMockApiServer() *httptest.Server {
+// NewMockAPIServer returns a mocked docker api server that responds to some fixed requests
+// used in the test suite.
+func NewMockAPIServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
logrus.Debug("Mock server has received a HTTP call on ", r.URL)
var response = ""
if isRequestFor("containers/json?limit=0", r) {
- response = getMockJsonFromDisk("./mocks/data/containers.json")
+ response = getMockJSONFromDisk("./mocks/data/containers.json")
} else if isRequestFor("ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", r) {
- response = getMockJsonFromDisk("./mocks/data/container_stopped.json")
+ response = getMockJSONFromDisk("./mocks/data/container_stopped.json")
} else if isRequestFor("b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", r) {
- response = getMockJsonFromDisk("./mocks/data/container_running.json")
+ response = getMockJSONFromDisk("./mocks/data/container_running.json")
} else if isRequestFor("sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd", r) {
- response = getMockJsonFromDisk("./mocks/data/image01.json")
+ response = getMockJSONFromDisk("./mocks/data/image01.json")
} else if isRequestFor("sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", r) {
- response = getMockJsonFromDisk("./mocks/data/image02.json")
+ response = getMockJSONFromDisk("./mocks/data/image02.json")
}
fmt.Fprintln(w, response)
},
@@ -36,7 +38,7 @@ func isRequestFor(urlPart string, r *http.Request) bool {
return strings.Contains(r.URL.String(), urlPart)
}
-func getMockJsonFromDisk(relPath string) string {
+func getMockJSONFromDisk(relPath string) string {
absPath, _ := filepath.Abs(relPath)
logrus.Error(absPath)
buf, err := ioutil.ReadFile(absPath)
From 498d500657dd189ee76a1af53ad96a2a034abe91 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 17 Apr 2019 21:44:31 +0200
Subject: [PATCH 110/761] exclude markdown files from coverage analysis
---
.codacy.yml | 6 ++++++
go.mod | 14 ++++++++++++++
go.sum | 37 +++++++++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+)
create mode 100644 .codacy.yml
diff --git a/.codacy.yml b/.codacy.yml
new file mode 100644
index 0000000..c117878
--- /dev/null
+++ b/.codacy.yml
@@ -0,0 +1,6 @@
+---
+engines:
+ coverage:
+ exclude_paths:
+ - "*.md"
+ - "**/*.md"
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 005ed9b..0d7ae0e 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,8 @@ require (
github.com/Microsoft/go-winio v0.4.12
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
+ github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111
+ github.com/containerd/containerd v1.2.6 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
github.com/davecgh/go-spew v1.1.1
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
@@ -17,20 +19,29 @@ require (
github.com/docker/go-connections v0.4.0
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82
github.com/docker/go-units v0.3.3
+ github.com/docker/swarmkit v1.12.0 // indirect
github.com/gogo/protobuf v1.2.1
+ github.com/golang/mock v1.1.1
github.com/golang/protobuf v1.3.1
+ github.com/google/go-cmp v0.2.0 // indirect
github.com/gorilla/mux v1.7.0
+ github.com/hashicorp/go-memdb v1.0.0 // indirect
github.com/hashicorp/go-version v1.1.0
github.com/inconshreveable/mousetrap v1.0.0
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
github.com/konsorten/go-windows-terminal-sequences v1.0.2
+ github.com/mattn/go-shellwords v1.0.5 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
+ github.com/onsi/ginkgo v1.8.0
+ github.com/onsi/gomega v1.5.0
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc v0.1.1
+ github.com/opencontainers/runtime-spec v1.0.1 // indirect
+ github.com/opencontainers/selinux v1.2.1 // indirect
github.com/pkg/errors v0.8.1
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v0.9.2
@@ -45,9 +56,12 @@ require (
github.com/stretchr/testify v1.3.0
github.com/theupdateframework/notary v0.6.1
github.com/urfave/cli v1.20.0
+ github.com/vbatts/tar-split v0.11.1 // indirect
golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
+ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
google.golang.org/grpc v1.19.1
+ gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index e664155..b0415fc 100644
--- a/go.sum
+++ b/go.sum
@@ -10,7 +10,11 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111 h1:gRfsoKtF1tba+hVsNgo7OKG7a35hBK30ouOTHPgqFf8=
+github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111/go.mod h1:H1ipqq0hhUWJgVeQ5dbUe/C8YptJrE/VGDQp9bI+qTo=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/containerd/containerd v1.2.6 h1:K38ZSAA9oKSrX3iFNY+4SddZ8hH1TCMCerc8NHfcKBQ=
+github.com/containerd/containerd v1.2.6/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -32,6 +36,10 @@ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zF
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/swarmkit v1.12.0 h1:vcbNXevt9xOod0miQxkp9WZ70IsOCe8geXkmFnXP2e0=
+github.com/docker/swarmkit v1.12.0/go.mod h1:n3Z4lIEl7g261ptkGDBcYi/3qBMDl9csaAhwi2MPejs=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -39,14 +47,23 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIUjTQ=
+github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
@@ -60,18 +77,27 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/opencontainers/selinux v1.2.1 h1:Svlc+L67YcjN4K2bqD8Wlw9jtMlmZ+1FEGn6zsm8am0=
+github.com/opencontainers/selinux v1.2.1/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -109,6 +135,7 @@ github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692 h1:GRhHqDOgeDr6QDTtq9gn2O4iKvm5dsbfqD/TXb0KLX0=
@@ -118,6 +145,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -127,14 +155,18 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -148,5 +180,10 @@ google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
From fcdd601389287f6f94aae8b14cb12005b7f723d0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Thu, 18 Apr 2019 17:06:17 +0200
Subject: [PATCH 111/761] update tls example
closes #277
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 9c6f7b0..5351b2e 100644
--- a/README.md
+++ b/README.md
@@ -242,6 +242,7 @@ With the certificates mounted into the watchtower container you need to specify
docker run -d \
--name watchtower \
-e DOCKER_HOST=$DOCKER_HOST \
+ -e DOCKER_CERT_PATH=/etc/ssl/docker \
-v $DOCKER_CERT_PATH:/etc/ssl/docker \
containrrr/watchtower --tlsverify
```
From 90bd4a1e3e15331b6ecd05f9ac34824f184827be Mon Sep 17 00:00:00 2001
From: Sven Gottwald <2502366+svengo@users.noreply.github.com>
Date: Sat, 20 Apr 2019 14:24:53 +0200
Subject: [PATCH 112/761] Add Dockerfile.self-contained (#283)
* Add Dockerfile using multi-stage build
* Rename Dockerfile and use more RUN commands
---
Dockerfile.self-contained | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 Dockerfile.self-contained
diff --git a/Dockerfile.self-contained b/Dockerfile.self-contained
new file mode 100644
index 0000000..64d5dc0
--- /dev/null
+++ b/Dockerfile.self-contained
@@ -0,0 +1,38 @@
+#
+# Builder
+#
+
+FROM golang:alpine as builder
+
+# use version (for example "v0.3.3") or "master"
+ARG WATCHTOWER_VERSION=master
+
+RUN apk add --no-cache \
+ alpine-sdk \
+ ca-certificates \
+ git \
+ tzdata
+
+RUN git clone --branch "${WATCHTOWER_VERSION}" https://github.com/containrrr/watchtower.git
+
+RUN \
+ cd watchtower && \
+ \
+ GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' . && \
+ GO111MODULE=on go test ./... -v
+
+
+#
+# watchtower
+#
+
+FROM scratch
+
+LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other container
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=builder /go/watchtower/watchtower /watchtower
+
+ENTRYPOINT ["/watchtower"]
From 46ffa16ee27ebb3ffa4c497e5bd18e27812e1afe Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 20 Apr 2019 16:44:41 +0200
Subject: [PATCH 113/761] add tests for check action, resolve wt cleanup bug
(#284)
add unit tests for the check action to allow for some
refactoring and bug fixing without having to worry about
breaking stuff.
resolve watchtower cleanup bug by adding an initial 1 second sleep
in the check action. without the sleep, the docker client returns an
empty array, which is why we were left with two watchtowers.
---
actions/actions_suite_test.go | 173 ++++++++++++++++++++++++++++++++++
actions/check.go | 76 ++++++++++++---
container/container.go | 8 +-
main.go | 2 +-
4 files changed, 239 insertions(+), 20 deletions(-)
create mode 100644 actions/actions_suite_test.go
diff --git a/actions/actions_suite_test.go b/actions/actions_suite_test.go
new file mode 100644
index 0000000..850fd4e
--- /dev/null
+++ b/actions/actions_suite_test.go
@@ -0,0 +1,173 @@
+package actions_test
+
+import (
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/containrrr/watchtower/actions"
+ "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/container/mocks"
+ "github.com/docker/docker/api/types"
+
+ cli "github.com/docker/docker/client"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+func TestActions(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Actions Suite")
+}
+
+var _ = Describe("the actions package", func() {
+ var dockerClient cli.CommonAPIClient
+ var client mockClient
+ BeforeSuite(func() {
+ server := mocks.NewMockAPIServer()
+ dockerClient, _ = cli.NewClientWithOpts(
+ cli.WithHost(server.URL),
+ cli.WithHTTPClient(server.Client()))
+ })
+ BeforeEach(func() {
+ client = mockClient{
+ api: dockerClient,
+ pullImages: false,
+ TestData: &TestData{},
+ }
+ })
+
+ Describe("the check prerequisites method", func() {
+ When("given an empty array", func() {
+ It("should not do anything", func() {
+ client.TestData.Containers = []container.Container{}
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ Expect(err).NotTo(HaveOccurred())
+ })
+ })
+ When("given an array of one", func() {
+ It("should not do anything", func() {
+ client.TestData.Containers = []container.Container{
+ createMockContainer(
+ "test-container",
+ "test-container",
+ "watchtower",
+ time.Now()),
+ }
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ Expect(err).NotTo(HaveOccurred())
+ })
+ })
+ When("given multiple containers", func() {
+ BeforeEach(func() {
+ client = mockClient{
+ api: dockerClient,
+ pullImages: false,
+ TestData: &TestData{
+ NameOfContainerToKeep: "test-container-02",
+ Containers: []container.Container{
+ createMockContainer(
+ "test-container-01",
+ "test-container-01",
+ "watchtower",
+ time.Now().AddDate(0, 0, -1)),
+ createMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "watchtower",
+ time.Now()),
+ },
+ },
+ }
+ })
+ It("should stop all but the latest one", func() {
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ Expect(err).NotTo(HaveOccurred())
+ })
+ })
+ When("deciding whether to cleanup images", func() {
+ BeforeEach(func() {
+ client = mockClient{
+ api: dockerClient,
+ pullImages: false,
+ TestData: &TestData{
+ Containers: []container.Container{
+ createMockContainer(
+ "test-container-01",
+ "test-container-01",
+ "watchtower",
+ time.Now().AddDate(0, 0, -1)),
+ createMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "watchtower",
+ time.Now()),
+ },
+ },
+ }
+ })
+ It("should try to delete the image if the cleanup flag is true", func() {
+ err := actions.CheckForMultipleWatchtowerInstances(client, true)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImage).To(BeTrue())
+ })
+ It("should not try to delete the image if the cleanup flag is false", func() {
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImage).To(BeFalse())
+ })
+ })
+ })
+})
+
+func createMockContainer(id string, name string, image string, created time.Time) container.Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: id,
+ Image: image,
+ Name: name,
+ Created: created.String(),
+ },
+ }
+ return *container.NewContainer(&content, nil)
+}
+
+type mockClient struct {
+ TestData *TestData
+ api cli.CommonAPIClient
+ pullImages bool
+}
+
+type TestData struct {
+ TriedToRemoveImage bool
+ NameOfContainerToKeep string
+ Containers []container.Container
+}
+
+func (client mockClient) ListContainers(f container.Filter) ([]container.Container, error) {
+ return client.TestData.Containers, nil
+}
+
+func (client mockClient) StopContainer(c container.Container, d time.Duration) error {
+ if c.Name() == client.TestData.NameOfContainerToKeep {
+ return errors.New("tried to stop the instance we want to keep")
+ }
+ return nil
+}
+func (client mockClient) StartContainer(c container.Container) error {
+ panic("Not implemented")
+}
+
+func (client mockClient) RenameContainer(c container.Container, s string) error {
+ panic("Not implemented")
+}
+
+func (client mockClient) RemoveImage(c container.Container) error {
+ client.TestData.TriedToRemoveImage = true
+ return nil
+}
+
+func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
+ panic("Not implemented")
+}
diff --git a/actions/check.go b/actions/check.go
index a95133e..16fd42e 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -1,36 +1,84 @@
package actions
import (
+ "errors"
+ "fmt"
"sort"
+ "strings"
+ "time"
+
+ "github.com/opencontainers/runc/Godeps/_workspace/src/github.com/Sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/containrrr/watchtower/container"
)
-// CheckPrereqs will ensure that there are not multiple instances of the
-// watchtower running simultaneously. If multiple watchtower containers are
-// detected, this function will stop and remove all but the most recently
-// started container.
-func CheckPrereqs(client container.Client, cleanup bool) error {
+// CheckForMultipleWatchtowerInstances will ensure that there are not multiple instances of the
+// watchtower running simultaneously. If multiple watchtower containers are detected, this function
+// will stop and remove all but the most recently started container.
+func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool) error {
+ awaitDockerClient()
containers, err := client.ListContainers(container.WatchtowerContainersFilter)
+
if err != nil {
+ log.Fatal(err)
return err
}
- if len(containers) > 1 {
- log.Info("Found multiple running watchtower instances. Cleaning up")
- sort.Sort(container.ByCreated(containers))
+ if len(containers) <= 1 {
+ log.Debug("There are no additional watchtower containers")
+ return nil
+ }
- // Iterate over all containers execept the last one
- for _, c := range containers[0 : len(containers)-1] {
- client.StopContainer(c, 60)
+ log.Info("Found multiple running watchtower instances. Cleaning up.")
+ return cleanupExcessWatchtowers(containers, client, cleanup)
+}
- if cleanup {
- client.RemoveImage(c)
+func cleanupExcessWatchtowers(containers []container.Container, client container.Client, cleanup bool) error {
+ var cleanupErrors int
+ var stopErrors int
+
+ sort.Sort(container.ByCreated(containers))
+ allContainersExceptLast := containers[0 : len(containers)-1]
+
+ for _, c := range allContainersExceptLast {
+ if err := client.StopContainer(c, 60); err != nil {
+ // logging the original here as we're just returning a count
+ logrus.Error(err)
+ stopErrors++
+ continue
+ }
+
+ if cleanup == true {
+ if err := client.RemoveImage(c); err != nil {
+ // logging the original here as we're just returning a count
+ logrus.Error(err)
+ cleanupErrors++
}
}
}
- return nil
+ return createErrorIfAnyHaveOccurred(stopErrors, cleanupErrors)
+}
+
+func createErrorIfAnyHaveOccurred(c int, i int) error {
+ if c == 0 && i == 0 {
+ return nil
+ }
+
+ var output strings.Builder
+
+ if c > 0 {
+ output.WriteString(fmt.Sprintf("%d errors while stopping containers", c))
+ }
+ if i > 0 {
+ output.WriteString(fmt.Sprintf("%d errors while cleaning up images", c))
+ }
+ return errors.New(output.String())
+}
+
+func awaitDockerClient() {
+ log.Debug("Sleeping for a seconds to ensure the docker api client has been properly initialized.")
+ time.Sleep(1 * time.Second)
}
diff --git a/container/container.go b/container/container.go
index 7c4d948..b32d4aa 100644
--- a/container/container.go
+++ b/container/container.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strconv"
"strings"
- log "github.com/sirupsen/logrus"
+
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
)
@@ -102,9 +102,7 @@ func (c Container) Links() []string {
// identified by the presence of the "com.centurylinklabs.watchtower" label in
// the container metadata.
func (c Container) IsWatchtower() bool {
- log.Debugf("Checking if %s is a watchtower instance.", c.Name())
- wasWatchtower := ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
- return wasWatchtower
+ return ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
}
// StopSignal returns the custom stop signal (if any) that is encoded in the
@@ -189,4 +187,4 @@ func (c Container) hostConfig() *dockercontainer.HostConfig {
func ContainsWatchtowerLabel(labels map[string]string) bool {
val, ok := labels[watchtowerLabel]
return ok && val == "true"
-}
\ No newline at end of file
+}
diff --git a/main.go b/main.go
index d9042de..a9b35c7 100644
--- a/main.go
+++ b/main.go
@@ -106,7 +106,7 @@ func start(c *cli.Context) error {
return nil
}
- if err := actions.CheckPrereqs(client, cleanup); err != nil {
+ if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup); err != nil {
log.Fatal(err)
}
From 16bbe3ff95ece8beeb23ba65c3e1485bdea066cd Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 27 Apr 2019 23:23:55 +0200
Subject: [PATCH 114/761] Set theme jekyll-theme-tactile
---
_config.yml | 1 +
1 file changed, 1 insertion(+)
create mode 100644 _config.yml
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..259a24e
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-tactile
\ No newline at end of file
From 984d69ee1dde3324d543f70ab7971909791548e5 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 27 Apr 2019 23:24:37 +0200
Subject: [PATCH 115/761] Set theme jekyll-theme-minimal
---
_config.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_config.yml b/_config.yml
index 259a24e..2f7efbe 100644
--- a/_config.yml
+++ b/_config.yml
@@ -1 +1 @@
-theme: jekyll-theme-tactile
\ No newline at end of file
+theme: jekyll-theme-minimal
\ No newline at end of file
From 1631c8cc2eb8a5d69610825ba698cda2605965a1 Mon Sep 17 00:00:00 2001
From: The Gitter Badger
Date: Tue, 30 Apr 2019 08:03:25 -0500
Subject: [PATCH 116/761] Add a Gitter chat badge to README.md (#290)
* Add Gitter badge
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 5351b2e..2827bfe 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,9 @@
+
+
+
## Overview
From e584f8bfcfd1be713afe7c1bbc52f902d2163900 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 12 May 2019 09:29:52 +0200
Subject: [PATCH 117/761] Make it possible to use watchtower to update exited
or created containers as well (#289)
* feature/112: add additional tests that verify include-stopped
* feature/112: implement include-stopped
* feature/112: update readme and cli help
* feature/112: fix linting issues
* remove superfluous logging
---
README.md | 1 +
app/app.go | 10 +++-
container/client.go | 69 +++++++++++++++++++++-------
container/container.go | 7 +++
container/container_test.go | 33 +++++++++----
container/mocks/ApiServer.go | 7 ++-
container/mocks/data/containers.json | 2 +-
main.go | 5 +-
8 files changed, 101 insertions(+), 33 deletions(-)
diff --git a/README.md b/README.md
index 2827bfe..59100e3 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,7 @@ docker run --rm containrrr/watchtower --help
- `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
- `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
- `--monitor-only` Will only monitor for new images, not update the containers.
+- `--include-stopped` Will also include created and exited containers.
- `--help` Show documentation about the supported flags.
See below for options used to configure notifications.
diff --git a/app/app.go b/app/app.go
index 5218d5d..6a20e4f 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 created and exited containers",
+ EnvVar: "WATCHTOWER_INCLUDE_STOPPED",
},
}
}
diff --git a/container/client.go b/container/client.go
index 94f790e..70a6fb1 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,48 @@ 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")
+ if client.includeStopped {
+ log.Debug("Retrieving containers including stopped and exited")
+ } else {
+ log.Debug("Retrieving running containers")
+ }
- runningContainers, err := client.api.ContainerList(
+ filter := client.createListFilter()
+ containers, err := client.api.ContainerList(
bg,
- types.ContainerListOptions{})
-
+ types.ContainerListOptions{
+ Filters: filter,
+ })
+
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 +97,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 +116,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 +187,23 @@ func (client dockerClient) StartContainer(c Container) error {
}
- log.Debugf("Starting container %s (%s)", name, creation.ID)
+ return client.startContainerIfPreviouslyRunning(bg, c, creation)
- err = client.api.ContainerStart(bg, creation.ID, types.ContainerStartOptions{})
+}
+
+func (client dockerClient) startContainerIfPreviouslyRunning(bg context.Context, c Container, creation container.ContainerCreateCreatedBody) 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/container_test.go b/container/container_test.go
index 2543976..f9dd540 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -17,15 +17,16 @@ func TestContainer(t *testing.T) {
var _ = Describe("the container", func() {
Describe("the client", func() {
+ var docker *cli.Client
var client Client
BeforeSuite(func() {
server := mocks.NewMockAPIServer()
- c, _ := cli.NewClientWithOpts(
+ docker, _ = cli.NewClientWithOpts(
cli.WithHost(server.URL),
cli.WithHTTPClient(server.Client(),
))
client = dockerClient{
- api: c,
+ api: docker,
pullImages: false,
}
})
@@ -41,7 +42,7 @@ var _ = Describe("the container", func() {
})
When("listing containers with a filter matching nothing", func() {
It("should return an empty array", func() {
- filter := filterByNames([]string { "lollercoaster"}, noFilter)
+ filter := filterByNames([]string{"lollercoaster"}, noFilter)
containers, err := client.ListContainers(filter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 0).To(BeTrue())
@@ -55,13 +56,25 @@ var _ = Describe("the container", func() {
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
})
})
+ When(`listing containers with the "include stopped" option`, func() {
+ It("should return both stopped and running containers", func() {
+ client = dockerClient{
+ api: docker,
+ pullImages: false,
+ includeStopped: true,
+ }
+ containers, err := client.ListContainers(noFilter)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(len(containers) > 0).To(BeTrue())
+ })
+ })
})
When("asked for metadata", func() {
var c *Container
BeforeEach(func() {
- c = mockContainerWithLabels(map[string]string {
+ c = mockContainerWithLabels(map[string]string{
"com.centurylinklabs.watchtower.enable": "true",
- "com.centurylinklabs.watchtower": "true",
+ "com.centurylinklabs.watchtower": "true",
})
})
It("should return its name on calls to .Name()", func() {
@@ -84,7 +97,7 @@ var _ = Describe("the container", func() {
Expect(exists).NotTo(BeFalse())
})
It("should return false, true if present but not true on calls to .Enabled()", func() {
- c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower.enable": "false" })
+ c = mockContainerWithLabels(map[string]string{"com.centurylinklabs.watchtower.enable": "false"})
enabled, exists := c.Enabled()
Expect(enabled).To(BeFalse())
@@ -93,7 +106,7 @@ var _ = Describe("the container", func() {
Expect(exists).NotTo(BeFalse())
})
It("should return false, false if not present on calls to .Enabled()", func() {
- c = mockContainerWithLabels(map[string]string{ "lol": "false" })
+ c = mockContainerWithLabels(map[string]string{"lol": "false"})
enabled, exists := c.Enabled()
Expect(enabled).To(BeFalse())
@@ -102,7 +115,7 @@ var _ = Describe("the container", func() {
Expect(exists).NotTo(BeTrue())
})
It("should return false, false if present but not parsable .Enabled()", func() {
- c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower.enable": "falsy" })
+ c = mockContainerWithLabels(map[string]string{"com.centurylinklabs.watchtower.enable": "falsy"})
enabled, exists := c.Enabled()
Expect(enabled).To(BeFalse())
@@ -116,12 +129,12 @@ var _ = Describe("the container", func() {
Expect(isWatchtower).To(BeTrue())
})
It("should return false if the label is present but set to false", func() {
- c = mockContainerWithLabels(map[string]string{ "com.centurylinklabs.watchtower": "false" })
+ c = mockContainerWithLabels(map[string]string{"com.centurylinklabs.watchtower": "false"})
isWatchtower := c.IsWatchtower()
Expect(isWatchtower).To(BeFalse())
})
It("should return false if the label is not present", func() {
- c = mockContainerWithLabels(map[string]string{ "funny.label": "false" })
+ c = mockContainerWithLabels(map[string]string{"funny.label": "false"})
isWatchtower := c.IsWatchtower()
Expect(isWatchtower).To(BeFalse())
})
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
From 057cb7eaf46b5c0aabef07d2e43920e556ff4f40 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Fri, 24 May 2019 16:53:31 +0200
Subject: [PATCH 118/761] docs: add kopfkrieg as a contributor (#301)
* docs: update README.md
* docs: create .all-contributorsrc
---
.all-contributorsrc | 24 ++++++++++++++++++++++++
README.md | 13 +++++++++++++
2 files changed, 37 insertions(+)
create mode 100644 .all-contributorsrc
diff --git a/.all-contributorsrc b/.all-contributorsrc
new file mode 100644
index 0000000..87611ec
--- /dev/null
+++ b/.all-contributorsrc
@@ -0,0 +1,24 @@
+{
+ "files": [
+ "README.md"
+ ],
+ "imageSize": 100,
+ "commit": false,
+ "contributors": [
+ {
+ "login": "kopfkrieg",
+ "name": "Florian",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/5047813?v=4",
+ "profile": "https://kopfkrieg.org",
+ "contributions": [
+ "review",
+ "doc"
+ ]
+ }
+ ],
+ "contributorsPerLine": 7,
+ "projectName": "watchtower",
+ "projectOwner": "containrrr",
+ "repoType": "github",
+ "repoHost": "https://github.com"
+}
diff --git a/README.md b/README.md
index 59100e3..b30a6ca 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
+[](#contributors)
@@ -343,3 +344,15 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
containrrr/watchtower
```
+
+## Contributors
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
From 82ada7f9325b6c47750f93aaf95dd0817590a60a Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Fri, 24 May 2019 16:56:22 +0200
Subject: [PATCH 119/761] docs: add Codelica as a contributor (#302)
* docs: update README.md
* docs: create .all-contributorsrc
---
.all-contributorsrc | 10 ++++++++++
README.md | 3 ++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 87611ec..4b5c566 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -5,6 +5,16 @@
"imageSize": 100,
"commit": false,
"contributors": [
+ {
+ "login": "Codelica",
+ "name": "James",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/386101?v=4",
+ "profile": "http://codelica.com",
+ "contributions": [
+ "test",
+ "ideas"
+ ]
+ },
{
"login": "kopfkrieg",
"name": "Florian",
diff --git a/README.md b/README.md
index b30a6ca..05b7390 100644
--- a/README.md
+++ b/README.md
@@ -351,7 +351,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From ed43d70e11fc8afe520c675d5046d5dc23adb3b2 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Fri, 24 May 2019 17:21:51 +0200
Subject: [PATCH 120/761] docs: add stffabi as a contributor (#304)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 5 ++---
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 4b5c566..5a84984 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -24,6 +24,15 @@
"review",
"doc"
]
+ },
+ {
+ "login": "stffabi",
+ "name": "stffabi",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/9464631?v=4",
+ "profile": "https://github.com/stffabi",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 05b7390..5c8c3e3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-[](#contributors)
+[](#contributors)
@@ -351,8 +351,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From f80d8541fded0dec0eae0db065f3fbe33614649a Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Fri, 24 May 2019 17:25:16 +0200
Subject: [PATCH 121/761] docs: add rosscado as a contributor (#305)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 5a84984..ffaeda4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -25,6 +25,15 @@
"doc"
]
},
+ {
+ "login": "rosscado",
+ "name": "Ross Cadogan",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/16578183?v=4",
+ "profile": "https://github.com/rosscado",
+ "contributions": [
+ "code"
+ ]
+ },
{
"login": "stffabi",
"name": "stffabi",
diff --git a/README.md b/README.md
index 5c8c3e3..f014333 100644
--- a/README.md
+++ b/README.md
@@ -351,7 +351,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From c722730604b72dd9f6d8e71122daf4c1469b0396 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Fri, 24 May 2019 17:27:23 +0200
Subject: [PATCH 122/761] docs: add bdehamer as a contributor (#306)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 10 ++++++++++
README.md | 5 -----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index ffaeda4..1aee5c3 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -25,6 +25,16 @@
"doc"
]
},
+ {
+ "login": "bdehamer",
+ "name": "Brian DeHamer",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/398027?v=4",
+ "profile": "https://github.com/bdehamer",
+ "contributions": [
+ "code",
+ "maintenance"
+ ],
+ },
{
"login": "rosscado",
"name": "Ross Cadogan",
diff --git a/README.md b/README.md
index f014333..a966e72 100644
--- a/README.md
+++ b/README.md
@@ -349,11 +349,6 @@ docker run -d \
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
-
-
-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
From 64d18b288d3102d7f052ff96af3e9477c898e50b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 17:41:20 +0200
Subject: [PATCH 123/761] add all contributors
---
.all-contributorsrc | 237 +++++++++++++++++++++++++++++++++++++++++++-
README.md | 8 +-
2 files changed, 241 insertions(+), 4 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 1aee5c3..5d1c1c9 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -16,7 +16,7 @@
]
},
{
- "login": "kopfkrieg",
+ "login": "KopfKrieg",
"name": "Florian",
"avatar_url": "https://avatars2.githubusercontent.com/u/5047813?v=4",
"profile": "https://kopfkrieg.org",
@@ -33,7 +33,7 @@
"contributions": [
"code",
"maintenance"
- ],
+ ]
},
{
"login": "rosscado",
@@ -52,11 +52,242 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "ATCUSA",
+ "name": "Austin",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/3581228?v=4",
+ "profile": "https://github.com/ATCUSA",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "davidgardner11",
+ "name": "David Gardner",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/6181487?v=4",
+ "profile": "https://labs.ctl.io",
+ "contributions": [
+ "review",
+ "doc"
+ ]
+ },
+ {
+ "login": "dolanor",
+ "name": "Tanguy â§ Herrmann",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/928722?v=4",
+ "profile": "https://github.com/dolanor",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "rdamazio",
+ "name": "Rodrigo Damazio Bovendorp",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/997641?v=4",
+ "profile": "https://github.com/rdamazio",
+ "contributions": [
+ "code",
+ "doc"
+ ]
+ },
+ {
+ "login": "thelamer",
+ "name": "Ryan Kuba",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/1852688?v=4",
+ "profile": "https://www.taisun.io/",
+ "contributions": [
+ "infra"
+ ]
+ },
+ {
+ "login": "cnrmck",
+ "name": "cnrmck",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/22061955?v=4",
+ "profile": "https://github.com/cnrmck",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "haswalt",
+ "name": "Harry Walter",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/338588?v=4",
+ "profile": "http://harrywalter.co.uk",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Robotex",
+ "name": "Robotex",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/74515?v=4",
+ "profile": "http://projectsperanza.com",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "ubergesundheit",
+ "name": "Gerald Pape",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/1494211?v=4",
+ "profile": "http://geraldpape.io",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "fomk",
+ "name": "fomk",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/17636183?v=4",
+ "profile": "https://github.com/fomk",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "svengo",
+ "name": "Sven Gottwald",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/2502366?v=4",
+ "profile": "https://github.com/svengo",
+ "contributions": [
+ "infra"
+ ]
+ },
+ {
+ "login": "techknowlogick",
+ "name": "techknowlogick",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/164197?v=4",
+ "profile": "https://liberapay.com/techknowlogick/",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "waja",
+ "name": "waja",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/1449568?v=4",
+ "profile": "http://log.c5t.org/about/",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "salbertson",
+ "name": "Scott Albertson",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/154463?v=4",
+ "profile": "http://scottalbertson.com",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "huddlesj",
+ "name": "Jason Huddleston",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/11966535?v=4",
+ "profile": "https://github.com/huddlesj",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "napstr",
+ "name": "Napster",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/6048348?v=4",
+ "profile": "https://npstr.space/",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "darknode",
+ "name": "Maxim",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/809429?v=4",
+ "profile": "https://github.com/darknode",
+ "contributions": [
+ "code",
+ "doc"
+ ]
+ },
+ {
+ "login": "mxschmitt",
+ "name": "Max Schmitt",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/17984549?v=4",
+ "profile": "https://schmitt.cat",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "cron410",
+ "name": "cron410",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/3082899?v=4",
+ "profile": "https://github.com/cron410",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "Cardoso222",
+ "name": "Paulo Henrique",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/7026517?v=4",
+ "profile": "https://github.com/Cardoso222",
+ "contributions": [
+ "maintenance"
+ ]
+ },
+ {
+ "login": "belak",
+ "name": "Kaleb Elwert",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/107097?v=4",
+ "profile": "https://coded.io",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "wmbutler",
+ "name": "Bill Butler",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/1254810?v=4",
+ "profile": "https://github.com/wmbutler",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "mariotacke",
+ "name": "Mario Tacke",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/4942019?v=4",
+ "profile": "https://www.mariotacke.io",
+ "contributions": [
+ "maintenance"
+ ]
+ },
+ {
+ "login": "mrw34",
+ "name": "Mark Woodbridge",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/1101318?v=4",
+ "profile": "https://markwoodbridge.com",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "simskij",
+ "name": "Simon Aronsson",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/1596025?v=4",
+ "profile": "http://www.arcticbit.se",
+ "contributions": [
+ "code",
+ "maintenance",
+ "review"
+ ]
}
],
"contributorsPerLine": 7,
"projectName": "watchtower",
"projectOwner": "containrrr",
"repoType": "github",
- "repoHost": "https://github.com"
+ "repoHost": "https://github.com",
+ "commitConvention": "none"
}
diff --git a/README.md b/README.md
index a966e72..424d12d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-[](#contributors)
@@ -36,6 +35,9 @@
+
+
+
## Overview
@@ -349,6 +351,10 @@ docker run -d \
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
From d5dc0dac1fd725ccb9b76ed7a3cd8aac1f1befb0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 17:48:38 +0200
Subject: [PATCH 124/761] add probot: stale
---
.github/stale.yml | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 .github/stale.yml
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000..fa652da
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,11 @@
+daysUntilStale: 21
+daysUntilClose: 14
+exemptLabels:
+ - pinned
+ - security
+staleLabel: "Status: Awaiting user"
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+closeComment: false
From ea249d13b3dc432c45b740779eb12e3160a93084 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 17:58:16 +0200
Subject: [PATCH 125/761] add probot: welcome
---
.github/config.yml | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 .github/config.yml
diff --git a/.github/config.yml b/.github/config.yml
new file mode 100644
index 0000000..62993b5
--- /dev/null
+++ b/.github/config.yml
@@ -0,0 +1,11 @@
+newIssueWelcomeComment: >
+ Hi there!
+
+ Thanks a bunch for opening your first issue! :pray:
+ As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md)
+
+newPRWelcomeComment: >
+ Thanks for opening this pull request! Please check out our [contributing guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md) as well as our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md).
+
+firstPRMergeComment: >
+ Congrats on merging your first pull request! We are all very proud of you! :sparkles:
From 3eddaa02d605cef4248dbca74f44d616b4ef3a92 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 18:19:10 +0200
Subject: [PATCH 126/761] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 424d12d..0f9bb16 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@
-
+
@@ -357,4 +357,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
From e44d0164486ae5019dff2bf85b652c23a03c9723 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 18:22:28 +0200
Subject: [PATCH 127/761] Update .all-contributorsrc
---
.all-contributorsrc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 5d1c1c9..6f7b635 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -233,7 +233,7 @@
"avatar_url": "https://avatars3.githubusercontent.com/u/7026517?v=4",
"profile": "https://github.com/Cardoso222",
"contributions": [
- "maintenance"
+ "doc"
]
},
{
@@ -260,7 +260,7 @@
"avatar_url": "https://avatars2.githubusercontent.com/u/4942019?v=4",
"profile": "https://www.mariotacke.io",
"contributions": [
- "maintenance"
+ "code"
]
},
{
From 32dbc6e3f95e08dc76f8ec05c3035f4b0876afbf Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 May 2019 18:23:03 +0200
Subject: [PATCH 128/761] update emoji-key
---
.all-contributorsrc | 3 ++-
README.md | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 6f7b635..bfead33 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -50,7 +50,8 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/9464631?v=4",
"profile": "https://github.com/stffabi",
"contributions": [
- "code"
+ "code",
+ "maintenance"
]
},
{
diff --git a/README.md b/README.md
index 0f9bb16..007bf29 100644
--- a/README.md
+++ b/README.md
@@ -353,7 +353,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From 22dc77efb27d057d600308ceccaf52c1cfad4a70 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 25 May 2019 13:46:41 +0200
Subject: [PATCH 129/761] Fix layout
---
README.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 137 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 007bf29..245111d 100644
--- a/README.md
+++ b/README.md
@@ -143,27 +143,149 @@ When no arguments are specified, watchtower will monitor all running containers.
### Options
-Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string:
+Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string, for example:
```bash
docker run --rm containrrr/watchtower --help
```
-- `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
-- `--run-once` Run an update attempt against a container name list one time immediately and exit.
-- `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
-- `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
-- `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
-- `--stop-timeout` Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
-- `--label-enable` Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
-- `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
-- `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.
-- `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
-- `--monitor-only` Will only monitor for new images, not update the containers.
-- `--include-stopped` Will also include created and exited containers.
-- `--help` Show documentation about the supported flags.
-See below for options used to configure notifications.
+#### Help
+
+
+
+
+ Argument |
+ Environment Variable |
+ Type |
+ Default |
+ Description |
+
+
+
+
+ --help |
+ - |
+ - |
+ - |
+ Shows documentation about the supported flags |
+
+
+ --cleanup |
+ WATCHTOWER_CLEANUP |
+ Boolean |
+ false |
+
+ Removes old images after updating. When this flag is specified, watchtower will remove the old image after
+ restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on
+ your system as containers are updated.
+ |
+
+
+ --debug |
+ - |
+ Boolean |
+ false |
+
+ Enable debug mode with verbose logging.
+ |
+
+
+ --host, -h |
+ DOCKER_HOST |
+ String |
+ "unix:///var/run/docker.sock" |
+
+ Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint
+ as "tcp://hostname:port".
+ |
+
+
+ --include-stopped |
+ WATCHTOWER_INCLUDE_STOPPED |
+ Boolean |
+ false |
+
+ Will also include created and exited containers.
+ |
+
+
+ ---interval, -i |
+ WATCHTOWER_POLL_INTERVAL |
+ Integer |
+ 300 |
+
+ Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
+ |
+
+
+ --label-enable |
+ WATCHTOWER_LABEL_ENABLE |
+ Boolean |
+ False |
+
+ Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
+ |
+
+
+ --monitor-only |
+ WATCHTOWER_MONITOR_ONLY |
+ Boolean |
+ False |
+
+ Will only monitor for new images, not update the containers.
+ |
+
+
+ --no-pull |
+ WATCHTOWER_NO_PULL |
+ Boolean |
+ False |
+
+ Do not pull new images. When this flag is specified, watchtower will not attempt to pull
+ new images from the registry. Instead it will only monitor the local image cache for changes.
+ Use this option if you are building new images directly on the Docker host without pushing
+ them to a registry.
+ |
+
+
+ --run-once |
+ WATCHTOWER_RUN_ONCE |
+ Boolean |
+ False |
+
+ Run an update attempt against a container name list one time immediately and exit.
+ |
+
+
+ --schedule, -s |
+ WATCHTOWER_SCHEDULE |
+ String |
+ - |
+
+ [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
+ |
+
+
+ --stop-timeout |
+ WATCHTOWER_TIMEOUT |
+ Duration |
+ 10s |
+
+ Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
+ |
+
+
+ --tlsverify |
+ DOCKER_TLS_VERIFY |
+ Boolean |
+ false |
+
+ Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to configure notifications.
+ |
+
+
+
## Linked Containers
From f47dbfed49fb43abecf0c044daa647837585fdb9 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 25 May 2019 14:26:42 +0200
Subject: [PATCH 130/761] fix format
---
README.md | 301 +++++++++++++++++++++++++++++-------------------------
1 file changed, 164 insertions(+), 137 deletions(-)
diff --git a/README.md b/README.md
index 245111d..a5adb79 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,38 @@
+## Table of Contents
+
+- [Overview](#overview)
+- [Usage](#usage)
+- [Arguments](#arguments)
+- [Available Options](#available-options)
+ * [Help](#help)
+ * [Cleanup](#cleanup)
+ * [Debug](#debug)
+ * [Docker host](#docker-host)
+ * [Include stopped](#include-stopped)
+ * [Poll Interval](#poll-interval)
+ * [Filter by enable label](#filter-by-enable-label)
+ * [Without updating containers](#without-updating-containers)
+ * [Without pulling new images](#without-pulling-new-images)
+ * [Run once](#run-once)
+ * [Scheduling](#scheduling)
+ * [Wait until timeout](#wait-until-timeout)
+ * [TLS Verification](#tls-verification)
+- [Linked Containers](#linked-containers)
+- [Stopping Containers](#stopping-containers)
+- [Selectively Watching Containers](#selectively-watching-containers)
+- [Remote Hosts](#remote-hosts)
+ * [Secure Connections](#secure-connections)
+- [Updating Watchtower](#updating-watchtower)
+- [Notifications](#notifications)
+ * [Settings](#settings)
+ * [Notifications via E-Mail](#notifications-via-e-mail)
+ * [Notifications through Slack webhook](#notifications-through-slack-webhook)
+ * [Notifications via MSTeams incoming webhook](#notifications-via-msteams-incoming-webhook)
+- [Contributors](#contributors)
+
## Overview
Watchtower is an application that will monitor your running Docker containers and watch for changes to the images that those containers were originally started from. If watchtower detects that an image has changed, it will automatically restart the container using the new image.
@@ -116,7 +148,7 @@ services:
command: --interval 30
```
-### Arguments
+## Arguments
By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when launching watchtower.
@@ -141,7 +173,7 @@ In the example above, watchtower will execute an upgrade attempt on the containe
When no arguments are specified, watchtower will monitor all running containers.
-### Options
+## Available options
Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string, for example:
@@ -149,143 +181,138 @@ Any of the options described below can be passed to the watchtower process by se
docker run --rm containrrr/watchtower --help
```
+### Help
+Shows documentation about the supported flags.
-#### Help
+```
+ Argument: --help
+Environment Variable: N/A
+ Type: N/A
+ Default: N/A
+```
-
-
-
- Argument |
- Environment Variable |
- Type |
- Default |
- Description |
-
-
-
-
- --help |
- - |
- - |
- - |
- Shows documentation about the supported flags |
-
-
- --cleanup |
- WATCHTOWER_CLEANUP |
- Boolean |
- false |
-
- Removes old images after updating. When this flag is specified, watchtower will remove the old image after
- restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on
- your system as containers are updated.
- |
-
-
- --debug |
- - |
- Boolean |
- false |
-
- Enable debug mode with verbose logging.
- |
-
-
- --host, -h |
- DOCKER_HOST |
- String |
- "unix:///var/run/docker.sock" |
-
- Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint
- as "tcp://hostname:port".
- |
-
-
- --include-stopped |
- WATCHTOWER_INCLUDE_STOPPED |
- Boolean |
- false |
-
- Will also include created and exited containers.
- |
-
-
- ---interval, -i |
- WATCHTOWER_POLL_INTERVAL |
- Integer |
- 300 |
-
- Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
- |
-
-
- --label-enable |
- WATCHTOWER_LABEL_ENABLE |
- Boolean |
- False |
-
- Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
- |
-
-
- --monitor-only |
- WATCHTOWER_MONITOR_ONLY |
- Boolean |
- False |
-
- Will only monitor for new images, not update the containers.
- |
-
-
- --no-pull |
- WATCHTOWER_NO_PULL |
- Boolean |
- False |
-
- Do not pull new images. When this flag is specified, watchtower will not attempt to pull
- new images from the registry. Instead it will only monitor the local image cache for changes.
- Use this option if you are building new images directly on the Docker host without pushing
- them to a registry.
- |
-
-
- --run-once |
- WATCHTOWER_RUN_ONCE |
- Boolean |
- False |
-
- Run an update attempt against a container name list one time immediately and exit.
- |
-
-
- --schedule, -s |
- WATCHTOWER_SCHEDULE |
- String |
- - |
-
- [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
- |
-
-
- --stop-timeout |
- WATCHTOWER_TIMEOUT |
- Duration |
- 10s |
-
- Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
- |
-
-
- --tlsverify |
- DOCKER_TLS_VERIFY |
- Boolean |
- false |
-
- Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to configure notifications.
- |
-
-
-
+### Cleanup
+Removes old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
+
+```
+ Argument: --cleanup
+Environment Variable: WATCHTOWER_CLEANUP
+ Type: Boolean
+ Default: false
+```
+
+### Debug
+Enable debug mode with verbose logging.
+
+```
+ Argument: --debug
+Environment Variable: N/A
+ Type: Boolean
+ Default: false
+```
+
+### Docker host
+Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port".
+
+```
+ Argument: --host, -h
+Environment Variable: DOCKER_HOST
+ Type: String
+ Default: "unix:///var/run/docker.sock"
+```
+
+### Include stopped
+Will also include created and exited containers.
+
+```
+ Argument: --include-stopped
+Environment Variable: WATCHTOWER_INCLUDE_STOPPED
+ Type: Boolean
+ Default: false
+```
+
+### Poll Interval
+Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
+
+```
+ Argument: ---interval, -i
+Environment Variable: WATCHTOWER_POLL_INTERVAL
+ Type: Integer
+ Default: 300
+```
+
+### Filter by enable label
+Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
+
+```
+ Argument: --label-enable
+Environment Variable: WATCHTOWER_LABEL_ENABLE
+ Type: Boolean
+ Default: false
+```
+
+### Without updating containers
+Will only monitor for new images, not update the containers.
+
+```
+ Argument: --monitor-only
+Environment Variable: WATCHTOWER_MONITOR_ONLY
+ Type: Boolean
+ Default: false
+```
+
+### Without pulling new images
+Do not pull new images. When this flag is specified, watchtower will not attempt to pull
+new images from the registry. Instead it will only monitor the local image cache for changes.
+Use this option if you are building new images directly on the Docker host without pushing
+them to a registry.
+
+```
+ Argument: --no-pull
+Environment Variable: WATCHTOWER_NO_PULL
+ Type: Boolean
+ Default: false
+```
+
+### Run once
+Run an update attempt against a container name list one time immediately and exit.
+
+```
+ Argument: --run-once
+Environment Variable: WATCHTOWER_RUN_ONCE
+ Type: Boolean
+ Default: false
+```
+
+### Scheduling
+[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
+
+```
+ Argument: --schedule, -s
+Environment Variable: WATCHTOWER_SCHEDULE
+ Type: String
+ Default: -
+```
+
+### Wait until timeout
+Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
+
+```
+ Argument: --stop-timeout
+Environment Variable: WATCHTOWER_TIMEOUT
+ Type: Duration
+ Default: 10s
+```
+
+### TLS Verification
+Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to configure notifications.
+
+```
+ Argument: --tlsverify
+Environment Variable: DOCKER_TLS_VERIFY
+ Type: Boolean
+ Default: false
+```
## Linked Containers
From d7775cde56955d73f1c9c2b106a453d393c991b1 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Sat, 25 May 2019 14:34:11 +0200
Subject: [PATCH 131/761] docs: add Ansem93 as a contributor (#329)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index bfead33..c9af3ec 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -283,6 +283,15 @@
"maintenance",
"review"
]
+ },
+ {
+ "login": "Ansem93",
+ "name": "Ansem93",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/6626218?v=4",
+ "profile": "https://github.com/Ansem93",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index a5adb79..7b8b2b0 100644
--- a/README.md
+++ b/README.md
@@ -502,7 +502,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From de844895cb130c32f9a26769cafee39fb148f711 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 25 May 2019 14:37:47 +0200
Subject: [PATCH 132/761] Ansem93 patch 1 (#330)
* Update option description
I added order alphabetically the options, adding information like Environment Variable. I took the informations from app.go file.
* fix format
From f9e02d8669bf16abf4c5934c029004d842c85417 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 31 May 2019 10:31:04 +0200
Subject: [PATCH 133/761] Update stale.yml
---
.github/stale.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/stale.yml b/.github/stale.yml
index fa652da..f413e47 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -3,7 +3,7 @@ daysUntilClose: 14
exemptLabels:
- pinned
- security
-staleLabel: "Status: Awaiting user"
+staleLabel: "Status: Stale"
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
From 985a9220993d09f0bb0232a558447773ac6006dd Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 31 May 2019 14:54:05 +0200
Subject: [PATCH 134/761] Add sponsorship option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently not accepting cash donations. However, If you'd like to support the project; feel free to purchase one of the books on my amazon wishlist: đ
---
.github/FUNDING.yml | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .github/FUNDING.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..364d198
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://www.amazon.com/hz/wishlist/ls/F94JJV822VX6
From 34b765960c0afd7c3bc3fc7bb9ca4dd474541698 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 1 Jun 2019 14:33:31 +0200
Subject: [PATCH 135/761] move documentation from readme to gh pages
---
.circleci/config.yml | 28 +++
.gitignore | 4 +-
README.md | 458 +------------------------------------------
mkdocs.yml | 22 +++
4 files changed, 61 insertions(+), 451 deletions(-)
create mode 100644 mkdocs.yml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8dff539..5b134e5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,6 +1,10 @@
version: 2.1
executors:
+ py:
+ docker:
+ - image: circleci/python:latest
+ working_directory: ~/repo
go:
docker:
- image: circleci/golang:latest
@@ -50,6 +54,15 @@ workflows:
ignore: /.*/
tags:
only: /^v[0-9]+(\.[0-9]+)*$/
+ - publish-docs:
+ requires:
+ - testing
+ - linting
+ filters:
+ branches:
+ ignore: /.*/
+ tags:
+ only: /^v[0-9]+(\.[0-9]+)*$/
jobs:
checkout:
executor: go
@@ -203,3 +216,18 @@ jobs:
-e DOCKER_REPOSITORY=containrrr/watchtower \
-e GIT_BRANCH=master \
lsiodev/readme-sync bash -c 'node sync'
+ publish-docs:
+ executor: py
+ steps:
+ - attach_workspace:
+ at: .
+ - run:
+ name: Install prerequisites
+ command: |
+ pip install \
+ mkdocs \
+ mkdocs-material \
+ md-toc
+ - run:
+ name: Generate and publish
+ command: mkdocs gh-deploy
diff --git a/.gitignore b/.gitignore
index 8195f6f..ab5a551 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@ vendor
.glide
dist
.idea
-.DS_Store
\ No newline at end of file
+.DS_Store
+/docs
+/site
\ No newline at end of file
diff --git a/README.md b/README.md
index 7b8b2b0..4289af4 100644
--- a/README.md
+++ b/README.md
@@ -40,461 +40,19 @@
-## Table of Contents
+## Quick Start
-- [Overview](#overview)
-- [Usage](#usage)
-- [Arguments](#arguments)
-- [Available Options](#available-options)
- * [Help](#help)
- * [Cleanup](#cleanup)
- * [Debug](#debug)
- * [Docker host](#docker-host)
- * [Include stopped](#include-stopped)
- * [Poll Interval](#poll-interval)
- * [Filter by enable label](#filter-by-enable-label)
- * [Without updating containers](#without-updating-containers)
- * [Without pulling new images](#without-pulling-new-images)
- * [Run once](#run-once)
- * [Scheduling](#scheduling)
- * [Wait until timeout](#wait-until-timeout)
- * [TLS Verification](#tls-verification)
-- [Linked Containers](#linked-containers)
-- [Stopping Containers](#stopping-containers)
-- [Selectively Watching Containers](#selectively-watching-containers)
-- [Remote Hosts](#remote-hosts)
- * [Secure Connections](#secure-connections)
-- [Updating Watchtower](#updating-watchtower)
-- [Notifications](#notifications)
- * [Settings](#settings)
- * [Notifications via E-Mail](#notifications-via-e-mail)
- * [Notifications through Slack webhook](#notifications-through-slack-webhook)
- * [Notifications via MSTeams incoming webhook](#notifications-via-msteams-incoming-webhook)
-- [Contributors](#contributors)
-
-## Overview
-
-Watchtower is an application that will monitor your running Docker containers and watch for changes to the images that those containers were originally started from. If watchtower detects that an image has changed, it will automatically restart the container using the new image.
-
-With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially.
-
-For example, let's say you were running watchtower along with an instance of _centurylink/wetty-cli_ image:
-
-```bash
-$ docker ps
-CONTAINER ID IMAGE STATUS PORTS NAMES
-967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
-6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
-```
-
-Every few minutes watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
-
-## Usage
-
-Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
-
-Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the -v flag when you run it.
-
-Run the `watchtower` container with the following command:
-
-```bash
-docker run -d \
- --name watchtower \
- -v /var/run/docker.sock:/var/run/docker.sock \
- containrrr/watchtower
-```
-
-If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
-or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
-
-Passing environment variables:
-
-```bash
-docker run -d \
- --name watchtower \
- -e REPO_USER=username \
- -e REPO_PASS=password \
- -v /var/run/docker.sock:/var/run/docker.sock \
- containrrr/watchtower container_to_watch --debug
-```
-
-Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
-
-Mounting the host's docker config file:
-
-```bash
-docker run -d \
- --name watchtower \
- -v /home//.docker/config.json:/config.json \
- -v /var/run/docker.sock:/var/run/docker.sock \
- containrrr/watchtower container_to_watch --debug
-```
-
-If you mount the config file as described above, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
-
-```json
-version: "3"
-services:
- cavo:
- image: index.docker.io//:
- ports:
- - "443:3443"
- - "80:3080"
- watchtower:
- image: containrrr/watchtower
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- - /root/.docker/config.json:/config.json
- command: --interval 30
-```
-
-## Arguments
-
-By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when launching watchtower.
-
-```bash
-docker run -d \
- --name watchtower \
- -v /var/run/docker.sock:/var/run/docker.sock \
- containrrr/watchtower nginx redis
-```
-
-In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other running containers will be ignored.
-
-If you do not want watchtower to run as a daemon you can pass a run-once flag and remove the watchtower container after it's execution.
-
-```bash
-docker run --rm \
--v /var/run/docker.sock:/var/run/docker.sock \
-containrrr/watchtower --run-once nginx redis
-```
-
-In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
-
-When no arguments are specified, watchtower will monitor all running containers.
-
-## Available options
-
-Any of the options described below can be passed to the watchtower process by setting them after the image name in the `docker run` string, for example:
-
-```bash
-docker run --rm containrrr/watchtower --help
-```
-
-### Help
-Shows documentation about the supported flags.
+With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially. Run the watchtower container with the following command:
```
- Argument: --help
-Environment Variable: N/A
- Type: N/A
- Default: N/A
+$ docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower
```
-### Cleanup
-Removes old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
-
-```
- Argument: --cleanup
-Environment Variable: WATCHTOWER_CLEANUP
- Type: Boolean
- Default: false
-```
-
-### Debug
-Enable debug mode with verbose logging.
-
-```
- Argument: --debug
-Environment Variable: N/A
- Type: Boolean
- Default: false
-```
-
-### Docker host
-Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port".
-
-```
- Argument: --host, -h
-Environment Variable: DOCKER_HOST
- Type: String
- Default: "unix:///var/run/docker.sock"
-```
-
-### Include stopped
-Will also include created and exited containers.
-
-```
- Argument: --include-stopped
-Environment Variable: WATCHTOWER_INCLUDE_STOPPED
- Type: Boolean
- Default: false
-```
-
-### Poll Interval
-Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
-
-```
- Argument: ---interval, -i
-Environment Variable: WATCHTOWER_POLL_INTERVAL
- Type: Integer
- Default: 300
-```
-
-### Filter by enable label
-Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
-
-```
- Argument: --label-enable
-Environment Variable: WATCHTOWER_LABEL_ENABLE
- Type: Boolean
- Default: false
-```
-
-### Without updating containers
-Will only monitor for new images, not update the containers.
-
-```
- Argument: --monitor-only
-Environment Variable: WATCHTOWER_MONITOR_ONLY
- Type: Boolean
- Default: false
-```
-
-### Without pulling new images
-Do not pull new images. When this flag is specified, watchtower will not attempt to pull
-new images from the registry. Instead it will only monitor the local image cache for changes.
-Use this option if you are building new images directly on the Docker host without pushing
-them to a registry.
-
-```
- Argument: --no-pull
-Environment Variable: WATCHTOWER_NO_PULL
- Type: Boolean
- Default: false
-```
-
-### Run once
-Run an update attempt against a container name list one time immediately and exit.
-
-```
- Argument: --run-once
-Environment Variable: WATCHTOWER_RUN_ONCE
- Type: Boolean
- Default: false
-```
-
-### Scheduling
-[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
-
-```
- Argument: --schedule, -s
-Environment Variable: WATCHTOWER_SCHEDULE
- Type: String
- Default: -
-```
-
-### Wait until timeout
-Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
-
-```
- Argument: --stop-timeout
-Environment Variable: WATCHTOWER_TIMEOUT
- Type: Duration
- Default: 10s
-```
-
-### TLS Verification
-Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to configure notifications.
-
-```
- Argument: --tlsverify
-Environment Variable: DOCKER_TLS_VERIFY
- Type: Boolean
- Default: false
-```
-
-## Linked Containers
-
-Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
-
-For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
-
-## Stopping Containers
-
-When watchtower detects that a running container needs to be updated it will stop the container by sending it a SIGTERM signal.
-If your container should be shutdown with a different signal you can communicate this to watchtower by setting a label named _com.centurylinklabs.watchtower.stop-signal_ with the value of the desired signal.
-
-This label can be coded directly into your image by using the `LABEL` instruction in your Dockerfile:
-
-```docker
-LABEL com.centurylinklabs.watchtower.stop-signal="SIGHUP"
-```
-
-Or, it can be specified as part of the `docker run` command line:
-
-```bash
-docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimage
-```
-
-## Selectively Watching Containers
-
-By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
-
-If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`.
-
-```docker
-LABEL com.centurylinklabs.watchtower.enable="false"
-```
-
-Or, it can be specified as part of the `docker run` command line:
-
-```bash
-docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
-```
-
-If you need to only include only some containers, pass the --label-enable flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of true for the containers you want to watch.
-
-```docker
-LABEL com.centurylinklabs.watchtower.enable="true"
-```
-
-Or, it can be specified as part of the `docker run` command line:
-
-```bash
-docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
-```
-
-## Remote Hosts
-
-By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container itself). However, it is possible to configure watchtower to monitor a remote Docker endpoint. When starting the watchtower container you can specify a remote Docker endpoint with either the `--host` flag or the `DOCKER_HOST` environment variable:
-
-```bash
-docker run -d \
- --name watchtower \
- containrrr/watchtower --host "tcp://10.0.1.2:2375"
-```
-
-or
-
-```bash
-docker run -d \
- --name watchtower \
- -e DOCKER_HOST="tcp://10.0.1.2:2375" \
- containrrr/watchtower
-```
-
-Note in both of the examples above that it is unnecessary to mount the _/var/run/docker.sock_ into the watchtower container.
-
-### Secure Connections
-
-Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used _docker-machine_ to provision your remote Docker host, you simply need to volume mount the certificates generated by _docker-machine_ into the watchtower container and optionally specify `--tlsverify` flag.
-
-The _docker-machine_ certificates for a particular host can be located by executing the `docker-machine env` command for the desired host (note the values for the `DOCKER_HOST` and `DOCKER_CERT_PATH` environment variables that are returned from this command). The directory containing the certificates for the remote host needs to be mounted into the watchtower container at _/etc/ssl/docker_.
-
-With the certificates mounted into the watchtower container you need to specify the `--tlsverify` flag to enable verification of the certificate:
-
-```bash
-docker run -d \
- --name watchtower \
- -e DOCKER_HOST=$DOCKER_HOST \
- -e DOCKER_CERT_PATH=/etc/ssl/docker \
- -v $DOCKER_CERT_PATH:/etc/ssl/docker \
- containrrr/watchtower --tlsverify
-```
-
-## Updating Watchtower
-
-If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted _/var/run/docker.sock_ into the watchtower container) then it has the ability to update itself. If a new version of the _containrrr/watchtower_ image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
-
-## Notifications
-
-Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
-The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
-
-- `email` to send notifications via e-mail
-- `slack` to send notifications through a Slack webhook
-- `msteams` to send notifications via MSTeams webhook
-
-### Settings
-
-- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
-
-### Notifications via E-Mail
-
-To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
-
-- `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
-- `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
-- `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
-- `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
-- `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
-- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
-- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
-
-Example:
-
-```bash
-docker run -d \
- --name watchtower \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -e WATCHTOWER_NOTIFICATIONS=email \
- -e WATCHTOWER_NOTIFICATION_EMAIL_FROM=fromaddress@gmail.com \
- -e WATCHTOWER_NOTIFICATION_EMAIL_TO=toaddress@gmail.com \
- -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
- -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
- -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
- containrrr/watchtower
-```
-
-### Notifications through Slack webhook
-
-To receive notifications in Slack, add `slack` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
-
-Additionally, you should set the Slack webhook url using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable.
-
-By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
-
-Other, optional, variables include:
-
-- `--notification-slack-channel` (env. `WATCHTOWER_NOTIFICATION_SLACK_CHANNEL`): A string which overrides the webhook's default channel. Example: #my-custom-channel.
-- `--notification-slack-icon-emoji` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI`): An [emoji code](https://www.webpagefx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.
-- `--notification-slack-icon-url` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_URL`): An icon image URL string to use in place of the default icon.
-
-Example:
-
-```bash
-docker run -d \
- --name watchtower \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -e WATCHTOWER_NOTIFICATIONS=slack \
- -e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
- -e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
- -e WATCHTOWER_NOTIFICATION_SLACK_CHANNEL=#my-custom-channel \
- -e WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI=:whale: \
- -e WATCHTOWER_NOTIFICATION_SLACK_ICON_URL= \
- containrrr/watchtower
-```
-
-### Notifications via MSTeams incoming webhook
-
-To receive notifications in MSTeams channel, add `msteams` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
-
-Additionally, you should set the MSTeams webhook url using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
-
-MSTeams notifier could send keys/values filled by `log.WithField` or `log.WithFields` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
-
-Example:
-
-```bash
-docker run -d \
- --name watchtower \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -e WATCHTOWER_NOTIFICATIONS=msteams \
- -e WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL="https://outlook.office.com/webhook/xxxxxxxx@xxxxxxx/IncomingWebhook/yyyyyyyy/zzzzzzzzzz" \
- -e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
- containrrr/watchtower
-```
+## Documentation
+The full documentation is available at https://containrrr.github.io/watchtower.
## Contributors
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..d64041b
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,22 @@
+site_name: Watchtower
+site_url: http://containrrr.github.io/watchtower/
+repo_url: https://github.com/containrrr/watchtower/
+theme:
+ name: 'material'
+markdown_extensions:
+ - toc:
+ permalink: True
+ separator: "_"
+nav:
+ - 'Home': 'index.md'
+ - 'Introduction': 'introduction.md'
+ - 'Usage overview': 'usage-overview.md'
+ - 'Arguments': 'arguments.md'
+ - 'Notifications': 'notifications.md'
+ - 'Container selection': 'container-selection.md'
+ - 'Linked containers': 'linked-containers.md'
+ - 'Remote hosts': 'remote-hosts.md'
+ - 'Secure connections': 'secure-connections.md'
+ - 'Stop signals': 'stop-signals.md'
+plugins:
+ - search
\ No newline at end of file
From d1f7c11f2040ff906679106220f86cff6bd154b7 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 2 Jun 2019 13:17:08 +0200
Subject: [PATCH 136/761] Delete _config.yml
---
_config.yml | 1 -
1 file changed, 1 deletion(-)
delete mode 100644 _config.yml
diff --git a/_config.yml b/_config.yml
deleted file mode 100644
index 2f7efbe..0000000
--- a/_config.yml
+++ /dev/null
@@ -1 +0,0 @@
-theme: jekyll-theme-minimal
\ No newline at end of file
From ac05caa609d206b502a08ccfeb3e733de1517e5c Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 2 Jun 2019 13:18:36 +0200
Subject: [PATCH 137/761] also keep the original markdown docs :P~
---
.gitignore | 3 +-
docs/arguments.md | 161 ++++++++++++++++++++++++++++++++++++
docs/container-selection.md | 25 ++++++
docs/index.md | 49 +++++++++++
docs/introduction.md | 15 ++++
docs/linked-containers.md | 3 +
docs/notifications.md | 92 +++++++++++++++++++++
docs/remote-hosts.md | 18 ++++
docs/secure-connections.md | 14 ++++
docs/stop-signals.md | 14 ++++
docs/usage-overview.md | 56 +++++++++++++
11 files changed, 448 insertions(+), 2 deletions(-)
create mode 100644 docs/arguments.md
create mode 100644 docs/container-selection.md
create mode 100644 docs/index.md
create mode 100644 docs/introduction.md
create mode 100644 docs/linked-containers.md
create mode 100644 docs/notifications.md
create mode 100644 docs/remote-hosts.md
create mode 100644 docs/secure-connections.md
create mode 100644 docs/stop-signals.md
create mode 100644 docs/usage-overview.md
diff --git a/.gitignore b/.gitignore
index ab5a551..fda8d42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,4 @@ vendor
dist
.idea
.DS_Store
-/docs
-/site
\ No newline at end of file
+/site
diff --git a/docs/arguments.md b/docs/arguments.md
new file mode 100644
index 0000000..b62fd50
--- /dev/null
+++ b/docs/arguments.md
@@ -0,0 +1,161 @@
+By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this
+will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you
+can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when
+launching watchtower.
+
+```bash
+$ docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower \
+ nginx redis
+```
+
+In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other
+running containers will be ignored. If you do not want watchtower to run as a daemon you can pass a run-once flag and remove
+the watchtower container after it's execution.
+
+```bash
+$ docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower \
+ --run-once \
+ nginx redis
+```
+
+In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
+
+When no arguments are specified, watchtower will monitor all running containers.
+
+## Help
+Shows documentation about the supported flags.
+
+```
+ Argument: --help
+Environment Variable: N/A
+ Type: N/A
+ Default: N/A
+```
+
+## Cleanup
+Removes old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
+
+```
+ Argument: --cleanup
+Environment Variable: WATCHTOWER_CLEANUP
+ Type: Boolean
+ Default: false
+```
+
+## Debug
+Enable debug mode with verbose logging.
+
+```
+ Argument: --debug
+Environment Variable: N/A
+ Type: Boolean
+ Default: false
+```
+
+## Docker host
+Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port".
+
+```
+ Argument: --host, -h
+Environment Variable: DOCKER_HOST
+ Type: String
+ Default: "unix:///var/run/docker.sock"
+```
+
+## Include stopped
+Will also include created and exited containers.
+
+```
+ Argument: --include-stopped
+Environment Variable: WATCHTOWER_INCLUDE_STOPPED
+ Type: Boolean
+ Default: false
+```
+
+## Poll Interval
+Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
+
+```
+ Argument: ---interval, -i
+Environment Variable: WATCHTOWER_POLL_INTERVAL
+ Type: Integer
+ Default: 300
+```
+
+## Filter by enable label
+Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
+
+```
+ Argument: --label-enable
+Environment Variable: WATCHTOWER_LABEL_ENABLE
+ Type: Boolean
+ Default: false
+```
+
+## Without updating containers
+Will only monitor for new images, not update the containers.
+
+```
+ Argument: --monitor-only
+Environment Variable: WATCHTOWER_MONITOR_ONLY
+ Type: Boolean
+ Default: false
+```
+
+## Without pulling new images
+Do not pull new images. When this flag is specified, watchtower will not attempt to pull
+new images from the registry. Instead it will only monitor the local image cache for changes.
+Use this option if you are building new images directly on the Docker host without pushing
+them to a registry.
+
+```
+ Argument: --no-pull
+Environment Variable: WATCHTOWER_NO_PULL
+ Type: Boolean
+ Default: false
+```
+
+## Run once
+Run an update attempt against a container name list one time immediately and exit.
+
+```
+ Argument: --run-once
+Environment Variable: WATCHTOWER_RUN_ONCE
+ Type: Boolean
+ Default: false
+```
+
+## Scheduling
+[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
+
+```
+ Argument: --schedule, -s
+Environment Variable: WATCHTOWER_SCHEDULE
+ Type: String
+ Default: -
+```
+
+## Wait until timeout
+Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
+
+```
+ Argument: --stop-timeout
+Environment Variable: WATCHTOWER_TIMEOUT
+ Type: Duration
+ Default: 10s
+```
+
+## TLS Verification
+Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to configure notifications.
+
+```
+ Argument: --tlsverify
+Environment Variable: DOCKER_TLS_VERIFY
+ Type: Boolean
+ Default: false
+```
diff --git a/docs/container-selection.md b/docs/container-selection.md
new file mode 100644
index 0000000..0e2cd20
--- /dev/null
+++ b/docs/container-selection.md
@@ -0,0 +1,25 @@
+By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
+
+If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`.
+
+```docker
+LABEL com.centurylinklabs.watchtower.enable="false"
+```
+
+Or, it can be specified as part of the `docker run` command line:
+
+```bash
+docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
+```
+
+If you need to only include only some containers, pass the --label-enable flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of true for the containers you want to watch.
+
+```docker
+LABEL com.centurylinklabs.watchtower.enable="true"
+```
+
+Or, it can be specified as part of the `docker run` command line:
+
+```bash
+docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
+```
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..c0bab4c
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,49 @@
+
+ Watchtower
+
+
+
+ A container-based solution for automating Docker container base image updates.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Quick Start
+
+With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially. Run the watchtower container with the following command:
+
+```
+$ docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower
+```
\ No newline at end of file
diff --git a/docs/introduction.md b/docs/introduction.md
new file mode 100644
index 0000000..9e0f5fe
--- /dev/null
+++ b/docs/introduction.md
@@ -0,0 +1,15 @@
+Watchtower is an application that will monitor your running Docker containers and watch for changes to the images that those containers were originally started from. If watchtower detects that an image has changed, it will automatically restart the container using the new image.
+
+With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially.
+
+For example, let's say you were running watchtower along with an instance of _centurylink/wetty-cli_ image:
+
+```bash
+$ docker ps
+CONTAINER ID IMAGE STATUS PORTS NAMES
+967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
+6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
+```
+
+Every few minutes watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
+
diff --git a/docs/linked-containers.md b/docs/linked-containers.md
new file mode 100644
index 0000000..133d3ca
--- /dev/null
+++ b/docs/linked-containers.md
@@ -0,0 +1,3 @@
+Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
+
+For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
\ No newline at end of file
diff --git a/docs/notifications.md b/docs/notifications.md
new file mode 100644
index 0000000..02f144a
--- /dev/null
+++ b/docs/notifications.md
@@ -0,0 +1,92 @@
+
+# Notifications
+
+Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
+The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
+
+- `email` to send notifications via e-mail
+- `slack` to send notifications through a Slack webhook
+- `msteams` to send notifications via MSTeams webhook
+
+## Settings
+
+- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
+
+## Available services
+
+### Email
+
+To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
+
+- `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
+- `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
+- `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
+- `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
+- `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
+- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
+- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=email \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_FROM=fromaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_TO=toaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
+ containrrr/watchtower
+```
+
+### Slack
+If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted _/var/run/docker.sock_ into the watchtower container) then it has the ability to update itself. If a new version of the _containrrr/watchtower_ image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
+
+To receive notifications in Slack, add `slack` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
+
+Additionally, you should set the Slack webhook url using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable.
+
+By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
+
+Other, optional, variables include:
+
+- `--notification-slack-channel` (env. `WATCHTOWER_NOTIFICATION_SLACK_CHANNEL`): A string which overrides the webhook's default channel. Example: #my-custom-channel.
+- `--notification-slack-icon-emoji` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI`): An [emoji code](https://www.webpagefx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.
+- `--notification-slack-icon-url` (env. `WATCHTOWER_NOTIFICATION_SLACK_ICON_URL`): An icon image URL string to use in place of the default icon.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=slack \
+ -e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
+ -e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
+ -e WATCHTOWER_NOTIFICATION_SLACK_CHANNEL=#my-custom-channel \
+ -e WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI=:whale: \
+ -e WATCHTOWER_NOTIFICATION_SLACK_ICON_URL= \
+ containrrr/watchtower
+```
+
+### Microsoft Teams
+
+To receive notifications in MSTeams channel, add `msteams` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
+
+Additionally, you should set the MSTeams webhook url using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
+
+MSTeams notifier could send keys/values filled by `log.WithField` or `log.WithFields` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=msteams \
+ -e WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL="https://outlook.office.com/webhook/xxxxxxxx@xxxxxxx/IncomingWebhook/yyyyyyyy/zzzzzzzzzz" \
+ -e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
+ containrrr/watchtower
+```
diff --git a/docs/remote-hosts.md b/docs/remote-hosts.md
new file mode 100644
index 0000000..e08fbd3
--- /dev/null
+++ b/docs/remote-hosts.md
@@ -0,0 +1,18 @@
+By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container itself). However, it is possible to configure watchtower to monitor a remote Docker endpoint. When starting the watchtower container you can specify a remote Docker endpoint with either the `--host` flag or the `DOCKER_HOST` environment variable:
+
+```bash
+docker run -d \
+ --name watchtower \
+ containrrr/watchtower --host "tcp://10.0.1.2:2375"
+```
+
+or
+
+```bash
+docker run -d \
+ --name watchtower \
+ -e DOCKER_HOST="tcp://10.0.1.2:2375" \
+ containrrr/watchtower
+```
+
+Note in both of the examples above that it is unnecessary to mount the _/var/run/docker.sock_ into the watchtower container.
\ No newline at end of file
diff --git a/docs/secure-connections.md b/docs/secure-connections.md
new file mode 100644
index 0000000..0c2a900
--- /dev/null
+++ b/docs/secure-connections.md
@@ -0,0 +1,14 @@
+Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used _docker-machine_ to provision your remote Docker host, you simply need to volume mount the certificates generated by _docker-machine_ into the watchtower container and optionally specify `--tlsverify` flag.
+
+The _docker-machine_ certificates for a particular host can be located by executing the `docker-machine env` command for the desired host (note the values for the `DOCKER_HOST` and `DOCKER_CERT_PATH` environment variables that are returned from this command). The directory containing the certificates for the remote host needs to be mounted into the watchtower container at _/etc/ssl/docker_.
+
+With the certificates mounted into the watchtower container you need to specify the `--tlsverify` flag to enable verification of the certificate:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -e DOCKER_HOST=$DOCKER_HOST \
+ -e DOCKER_CERT_PATH=/etc/ssl/docker \
+ -v $DOCKER_CERT_PATH:/etc/ssl/docker \
+ containrrr/watchtower --tlsverify
+```
diff --git a/docs/stop-signals.md b/docs/stop-signals.md
new file mode 100644
index 0000000..f4b4f1d
--- /dev/null
+++ b/docs/stop-signals.md
@@ -0,0 +1,14 @@
+When watchtower detects that a running container needs to be updated it will stop the container by sending it a SIGTERM signal.
+If your container should be shutdown with a different signal you can communicate this to watchtower by setting a label named _com.centurylinklabs.watchtower.stop-signal_ with the value of the desired signal.
+
+This label can be coded directly into your image by using the `LABEL` instruction in your Dockerfile:
+
+```docker
+LABEL com.centurylinklabs.watchtower.stop-signal="SIGHUP"
+```
+
+Or, it can be specified as part of the `docker run` command line:
+
+```bash
+docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimage
+```
diff --git a/docs/usage-overview.md b/docs/usage-overview.md
new file mode 100644
index 0000000..fcc2039
--- /dev/null
+++ b/docs/usage-overview.md
@@ -0,0 +1,56 @@
+Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
+
+Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the -v flag when you run it.
+
+Run the `watchtower` container with the following command:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower
+```
+
+If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
+or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
+
+Passing environment variables:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -e REPO_USER=username \
+ -e REPO_PASS=password \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower container_to_watch --debug
+```
+
+Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
+
+Mounting the host's docker config file:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /home//.docker/config.json:/config.json \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ containrrr/watchtower container_to_watch --debug
+```
+
+If you mount the config file as described above, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
+
+```json
+version: "3"
+services:
+ cavo:
+ image: index.docker.io//:
+ ports:
+ - "443:3443"
+ - "80:3080"
+ watchtower:
+ image: containrrr/watchtower
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /root/.docker/config.json:/config.json
+ command: --interval 30
+```
\ No newline at end of file
From 339df55cc63a2cabf835ab64ee725af9aca33d96 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 2 Jun 2019 13:29:37 +0200
Subject: [PATCH 138/761] fix: stop marking milestone issues as stale
---
.github/stale.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/stale.yml b/.github/stale.yml
index f413e47..f59106c 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,5 +1,6 @@
-daysUntilStale: 21
-daysUntilClose: 14
+daysUntilStale: 60
+daysUntilClose: 7
+exemptMilestones: true
exemptLabels:
- pinned
- security
From 71efb67060f5c11594637a28d970177c0a33b9bb Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Thu, 13 Jun 2019 20:46:51 +0000
Subject: [PATCH 139/761] docs: update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4289af4..0b34aec 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From 635e295ff41dad3bf71fbf96bb20e58f58760fe9 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
Date: Thu, 13 Jun 2019 20:46:52 +0000
Subject: [PATCH 140/761] docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index c9af3ec..f57ce74 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -292,6 +292,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "zoispag",
+ "name": "Zois Pagoulatos",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/21138205?v=4",
+ "profile": "https://github.com/zoispag",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From 998e8052c5211abf8b9ac4c41df79785ddaae751 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 22 Jun 2019 22:04:36 +0200
Subject: [PATCH 141/761] switch urfave to cobra
---
app/app.go | 168 ------------------------
cmd/root.go | 178 +++++++++++++++++++++++++
go.mod | 17 ++-
go.sum | 77 ++++++++++-
internal/flags/flags.go | 269 ++++++++++++++++++++++++++++++++++++++
main.go | 196 +--------------------------
notifications/email.go | 28 ++--
notifications/msteams.go | 11 +-
notifications/notifier.go | 12 +-
notifications/slack.go | 23 ++--
10 files changed, 581 insertions(+), 398 deletions(-)
delete mode 100644 app/app.go
create mode 100644 cmd/root.go
create mode 100644 internal/flags/flags.go
diff --git a/app/app.go b/app/app.go
deleted file mode 100644
index 6a20e4f..0000000
--- a/app/app.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package app
-
-import (
- "time"
-
- "github.com/urfave/cli"
-)
-
-// SetupCliFlags registers flags on the supplied urfave app
-func SetupCliFlags(app *cli.App) {
- app.Flags = []cli.Flag{
- cli.StringFlag{
- Name: "host, H",
- Usage: "daemon socket to connect to",
- Value: "unix:///var/run/docker.sock",
- EnvVar: "DOCKER_HOST",
- },
- cli.IntFlag{
- Name: "interval, i",
- Usage: "poll interval (in seconds)",
- Value: 300,
- EnvVar: "WATCHTOWER_POLL_INTERVAL",
- },
- cli.StringFlag{
- Name: "schedule, s",
- Usage: "the cron expression which defines when to update",
- EnvVar: "WATCHTOWER_SCHEDULE",
- },
- cli.BoolFlag{
- Name: "no-pull",
- Usage: "do not pull new images",
- EnvVar: "WATCHTOWER_NO_PULL",
- },
- cli.BoolFlag{
- Name: "no-restart",
- Usage: "do not restart containers",
- EnvVar: "WATCHTOWER_NO_RESTART",
- },
- cli.BoolFlag{
- Name: "cleanup",
- Usage: "remove old images after updating",
- EnvVar: "WATCHTOWER_CLEANUP",
- },
- cli.BoolFlag{
- Name: "tlsverify",
- Usage: "use TLS and verify the remote",
- EnvVar: "DOCKER_TLS_VERIFY",
- },
- cli.DurationFlag{
- Name: "stop-timeout",
- Usage: "timeout before container is forcefully stopped",
- Value: time.Second * 10,
- EnvVar: "WATCHTOWER_TIMEOUT",
- },
- cli.BoolFlag{
- Name: "label-enable",
- Usage: "watch containers where the com.centurylinklabs.watchtower.enable label is true",
- EnvVar: "WATCHTOWER_LABEL_ENABLE",
- },
- cli.BoolFlag{
- Name: "debug",
- Usage: "enable debug mode with verbose logging",
- },
- cli.StringSliceFlag{
- Name: "notifications",
- Value: &cli.StringSlice{},
- Usage: "notification types to send (valid: email, slack, msteams)",
- EnvVar: "WATCHTOWER_NOTIFICATIONS",
- },
- cli.StringFlag{
- Name: "notifications-level",
- Usage: "The log level used for sending notifications. Possible values: \"panic\", \"fatal\", \"error\", \"warn\", \"info\" or \"debug\"",
- EnvVar: "WATCHTOWER_NOTIFICATIONS_LEVEL",
- Value: "info",
- },
- cli.StringFlag{
- Name: "notification-email-from",
- Usage: "Address to send notification e-mails from",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_FROM",
- },
- cli.StringFlag{
- Name: "notification-email-to",
- Usage: "Address to send notification e-mails to",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_TO",
- },
- cli.StringFlag{
- Name: "notification-email-server",
- Usage: "SMTP server to send notification e-mails through",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER",
- },
- cli.IntFlag{
- Name: "notification-email-server-port",
- Usage: "SMTP server port to send notification e-mails through",
- Value: 25,
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT",
- },
- cli.BoolFlag{
- Name: "notification-email-server-tls-skip-verify",
- Usage: "Controls whether watchtower verifies the SMTP server's certificate chain and host name. " +
- "If set, TLS accepts any certificate " +
- "presented by the server and any host name in that certificate. " +
- "In this mode, TLS is susceptible to man-in-the-middle attacks. " +
- "This should be used only for testing.",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY",
- },
- cli.StringFlag{
- Name: "notification-email-server-user",
- Usage: "SMTP server user for sending notifications",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER",
- },
- cli.StringFlag{
- Name: "notification-email-server-password",
- Usage: "SMTP server password for sending notifications",
- EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
- },
- cli.StringFlag{
- Name: "notification-slack-hook-url",
- Usage: "The Slack Hook URL to send notifications to",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL",
- },
- cli.StringFlag{
- Name: "notification-slack-identifier",
- Usage: "A string which will be used to identify the messages coming from this watchtower instance. Default if omitted is \"watchtower\"",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER",
- Value: "watchtower",
- },
- cli.StringFlag{
- Name: "notification-slack-channel",
- Usage: "A string which overrides the webhook's default channel. Example: #my-custom-channel",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_CHANNEL",
- },
- cli.StringFlag{
- Name: "notification-slack-icon-emoji",
- Usage: "An emoji code string to use in place of the default icon",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI",
- },
- cli.StringFlag{
- Name: "notification-slack-icon-url",
- Usage: "An icon image URL string to use in place of the default icon",
- EnvVar: "WATCHTOWER_NOTIFICATION_SLACK_ICON_URL",
- },
- cli.StringFlag{
- Name: "notification-msteams-hook",
- Usage: "The MSTeams WebHook URL to send notifications to",
- EnvVar: "WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL",
- },
- cli.BoolFlag{
- Name: "notification-msteams-data",
- Usage: "The MSTeams notifier will try to extract log entry fields as MSTeams message facts",
- EnvVar: "WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA",
- },
- cli.BoolFlag{
- Name: "monitor-only",
- Usage: "Will only monitor for new images, not update the containers",
- EnvVar: "WATCHTOWER_MONITOR_ONLY",
- },
- cli.BoolFlag{
- Name: "run-once",
- Usage: "Run once now and exit",
- EnvVar: "WATCHTOWER_RUN_ONCE",
- },
- cli.BoolFlag{
- Name: "include-stopped",
- Usage: "Will also include created and exited containers",
- EnvVar: "WATCHTOWER_INCLUDE_STOPPED",
- },
- }
-}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..18e20ad
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,178 @@
+package cmd
+
+import (
+ "fmt"
+ "github.com/containrrr/watchtower/actions"
+ "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/internal/flags"
+ "github.com/containrrr/watchtower/notifications"
+ "github.com/robfig/cron"
+ log "github.com/sirupsen/logrus"
+ "os"
+ "os/signal"
+ "strconv"
+ "syscall"
+ "time"
+
+ "github.com/spf13/cobra"
+)
+
+const DockerAPIMinVersion string = "1.24"
+
+var (
+ client container.Client
+ scheduleSpec string
+ cleanup bool
+ noRestart bool
+ monitorOnly bool
+ enableLabel bool
+ notifier *notifications.Notifier
+ timeout time.Duration
+)
+
+var rootCmd = &cobra.Command{
+ Use: "watchtower",
+ Short: "Automatically updates running Docker containers",
+ Long: `
+Watchtower automatically updates running Docker containers whenever a new image is released.
+More information available at https://github.com/containrrr/watchtower/.
+`,
+ Run: Run,
+ PreRun: PreRun,
+}
+
+func init() {
+ flags.SetDefaults()
+ flags.RegisterDockerFlags(rootCmd)
+ flags.RegisterSystemFlags(rootCmd)
+ flags.RegisterNotificationFlags(rootCmd)
+}
+
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
+
+func PreRun(cmd *cobra.Command, args []string) {
+ f := cmd.PersistentFlags()
+
+ if enabled, _ := f.GetBool("debug"); enabled == true {
+ log.SetLevel(log.DebugLevel)
+ }
+
+ pollingSet := f.Changed("interval")
+ cronSet := f.Changed("schedule")
+ schedule, _ := f.GetString("schedule")
+ cronLen := len(schedule)
+
+ if pollingSet && cronSet && cronLen > 0 {
+ log.Fatal("Only schedule or interval can be defined, not both.")
+ } else if cronSet && cronLen > 0 {
+ scheduleSpec, _ = f.GetString("schedule")
+ } else {
+ interval, _ := f.GetInt("interval")
+ scheduleSpec = "@every " + strconv.Itoa(interval) + "s"
+ }
+
+ cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd)
+
+ if timeout < 0 {
+ log.Fatal("Please specify a positive value for timeout value.")
+ }
+ enableLabel, _ = f.GetBool("label-enable")
+
+ // configure environment vars for client
+ err := flags.EnvConfig(cmd, DockerAPIMinVersion)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ noPull, _ := f.GetBool("no-pull")
+ includeStopped, _ := f.GetBool("include-stopped")
+ client = container.NewClient(
+ !noPull,
+ includeStopped,
+ )
+
+ notifier = notifications.NewNotifier(cmd)
+}
+
+func Run(c *cobra.Command, names []string) {
+ filter := container.BuildFilter(names, enableLabel)
+ runOnce, _ := c.PersistentFlags().GetBool("run-once")
+
+ if runOnce {
+ log.Info("Running a one time update.")
+ runUpdatesWithNotifications(filter)
+ os.Exit(1)
+ return
+ }
+
+ if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup); err != nil {
+ log.Fatal(err)
+ }
+
+ runUpgradesOnSchedule(filter)
+ os.Exit(1)
+}
+
+func runUpgradesOnSchedule(filter container.Filter) error {
+ tryLockSem := make(chan bool, 1)
+ tryLockSem <- true
+
+ cron := cron.New()
+ err := cron.AddFunc(
+ scheduleSpec,
+ func() {
+ select {
+ case v := <-tryLockSem:
+ defer func() { tryLockSem <- v }()
+ runUpdatesWithNotifications(filter)
+ default:
+ log.Debug("Skipped another update already running.")
+ }
+
+ nextRuns := cron.Entries()
+ if len(nextRuns) > 0 {
+ log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
+ }
+ })
+
+ if err != nil {
+ return err
+ }
+
+ log.Debug("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ cron.Start()
+
+ // Graceful shut-down on SIGINT/SIGTERM
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, os.Interrupt)
+ signal.Notify(interrupt, syscall.SIGTERM)
+
+ <-interrupt
+ cron.Stop()
+ log.Info("Waiting for running update to be finished...")
+ <-tryLockSem
+ return nil
+}
+
+func runUpdatesWithNotifications(filter container.Filter) {
+ notifier.StartNotification()
+ updateParams := actions.UpdateParams{
+ Filter: filter,
+ Cleanup: cleanup,
+ NoRestart: noRestart,
+ Timeout: timeout,
+ MonitorOnly: monitorOnly,
+ }
+ err := actions.Update(client, updateParams)
+ if err != nil {
+ log.Println(err)
+ }
+ notifier.SendNotification()
+}
+
+
diff --git a/go.mod b/go.mod
index 0d7ae0e..1690237 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
github.com/Microsoft/go-winio v0.4.12
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
- github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
+ github.com/beorn7/perks v1.0.0
github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111
github.com/containerd/containerd v1.2.6 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
@@ -23,7 +23,6 @@ require (
github.com/gogo/protobuf v1.2.1
github.com/golang/mock v1.1.1
github.com/golang/protobuf v1.3.1
- github.com/google/go-cmp v0.2.0 // indirect
github.com/gorilla/mux v1.7.0
github.com/hashicorp/go-memdb v1.0.0 // indirect
github.com/hashicorp/go-version v1.1.0
@@ -44,24 +43,24 @@ require (
github.com/opencontainers/selinux v1.2.1 // indirect
github.com/pkg/errors v0.8.1
github.com/pmezard/go-difflib v1.0.0
- github.com/prometheus/client_golang v0.9.2
+ github.com/prometheus/client_golang v0.9.3
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
- github.com/prometheus/common v0.2.0
- github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872
+ github.com/prometheus/common v0.4.0
+ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/sirupsen/logrus v1.4.1
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
+ github.com/spf13/viper v1.4.0
github.com/stretchr/objx v0.1.1
github.com/stretchr/testify v1.3.0
github.com/theupdateframework/notary v0.6.1
- github.com/urfave/cli v1.20.0
github.com/vbatts/tar-split v0.11.1 // indirect
golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
- golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b
+ golang.org/x/net v0.0.0-20190522155817-f3200d17e092
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
- golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
+ google.golang.org/appengine v1.4.0 // indirect
google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
- google.golang.org/grpc v1.19.1
+ google.golang.org/grpc v1.21.0
gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index b0415fc..c9968f6 100644
--- a/go.sum
+++ b/go.sum
@@ -4,22 +4,34 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111 h1:gRfsoKtF1tba+hVsNgo7OKG7a35hBK30ouOTHPgqFf8=
github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111/go.mod h1:H1ipqq0hhUWJgVeQ5dbUe/C8YptJrE/VGDQp9bI+qTo=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/containerd v1.2.6 h1:K38ZSAA9oKSrX3iFNY+4SddZ8hH1TCMCerc8NHfcKBQ=
github.com/containerd/containerd v1.2.6/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29 h1:ciaXDHaWQda0nvevWqcjtXX/buQY3e0lga1vq8Batq0=
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
@@ -40,21 +52,29 @@ github.com/docker/swarmkit v1.12.0 h1:vcbNXevt9xOod0miQxkp9WZ70IsOCe8geXkmFnXP2e
github.com/docker/swarmkit v1.12.0/go.mod h1:n3Z4lIEl7g261ptkGDBcYi/3qBMDl9csaAhwi2MPejs=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIUjTQ=
github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
@@ -62,6 +82,8 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -70,6 +92,7 @@ github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -77,13 +100,21 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -98,6 +129,8 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.2.1 h1:Svlc+L67YcjN4K2bqD8Wlw9jtMlmZ+1FEGn6zsm8am0=
github.com/opencontainers/selinux v1.2.1/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -106,25 +139,44 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 h1:0aNv3xC7DmQoy1/x1sMh18g+fihWW68LL13i8ao9kl4=
github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -133,9 +185,15 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
-github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692 h1:GRhHqDOgeDr6QDTtq9gn2O4iKvm5dsbfqD/TXb0KLX0=
@@ -143,14 +201,19 @@ golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692/go.mod h1:WFFai1msRO1wXaE
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b h1:/zjbcJPEGAyu6Is/VBOALsgdi4z9+kz/Vtdm6S+beD0=
golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -160,16 +223,19 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -178,12 +244,19 @@ google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRn
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
new file mode 100644
index 0000000..236e4ba
--- /dev/null
+++ b/internal/flags/flags.go
@@ -0,0 +1,269 @@
+package flags
+
+import (
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+ "os"
+ "time"
+)
+
+func RegisterDockerFlags(rootCmd *cobra.Command) {
+ flags := rootCmd.PersistentFlags()
+ flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
+ flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
+}
+
+func RegisterSystemFlags(rootCmd *cobra.Command) {
+ flags := rootCmd.PersistentFlags()
+ flags.IntP(
+ "interval",
+ "i",
+ viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
+ "poll interval (in seconds)")
+
+ flags.StringP("schedule",
+ "s",
+ viper.GetString("WATCHTOWER_SCHEDULE"),
+ "the cron expression which defines when to update")
+
+ flags.DurationP("stop-timeout",
+ "t",
+ viper.GetDuration("WATCHTOWER_TIMEOUT"),
+ "timeout before a container is forcefully stopped")
+
+ flags.BoolP(
+ "no-pull",
+ "",
+ viper.GetBool("WATCHTOWER_NO_PULL"),
+ "do not pull any new images")
+
+ flags.BoolP(
+ "no-restart",
+ "",
+ viper.GetBool("WATCHTOWER_NO_RESTART"),
+ "do not restart any containers")
+
+ flags.BoolP(
+ "cleanup",
+ "c",
+ viper.GetBool("WATCHTOWER_CLEANUP"),
+ "remove previously used images after updating")
+
+ flags.BoolP(
+ "label-enable",
+ "e",
+ viper.GetBool("WATCHTOWER_LABEL_ENABLE"),
+ "watch containers where the com.centurylinklabs.watchtower.enable label is true")
+
+ flags.BoolP(
+ "debug",
+ "d",
+ viper.GetBool("WATCHTOWER_DEBUG"),
+ "enable debug mode with verbose logging")
+
+
+ flags.BoolP(
+ "monitor-only",
+ "m",
+ viper.GetBool("WATCHTOWER_MONITOR_ONLY"),
+ "Will only monitor for new images, not update the containers")
+
+ flags.BoolP(
+ "run-once",
+ "R",
+ viper.GetBool("WATCHTOWER_RUN_ONCE"),
+ "Run once now and exit")
+
+ flags.BoolP(
+ "include-stopped",
+ "S",
+ viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
+ "Will also include created and exited containers")
+}
+
+func RegisterNotificationFlags(rootCmd *cobra.Command) {
+ flags := rootCmd.PersistentFlags()
+
+ flags.StringSliceP(
+ "notifications",
+ "n",
+ viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
+ " notification types to send (valid: email, slack, msteams")
+
+ flags.StringP(
+ "notifications-level",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
+ "The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
+
+ flags.StringP(
+ "notification-email-from",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
+ "Address to send notification emails from")
+
+ flags.StringP(
+ "notification-email-to",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
+ "Address to send notification emails to")
+
+ flags.StringP(
+ "notification-email-server",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
+ "SMTP server to send notification emails through")
+
+ flags.StringP(
+ "notification-email-server-port",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
+ "SMTP server port to send notification emails through")
+
+ flags.BoolP(
+ "notification-email-server-tls-skip-verify",
+ "",
+ viper.GetBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"),
+ `
+Controls whether watchtower verifies the SMTP server's certificate chain and host name.
+Should only be used for testing.
+`)
+
+ flags.StringP(
+ "notification-email-server-user",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
+ "SMTP server user for sending notifications")
+
+ flags.StringP(
+ "notification-email-server-password",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
+ "SMTP server password for sending notifications")
+
+ flags.StringP(
+ "notification-slack-hook-url",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"),
+ "The Slack Hook URL to send notifications to")
+
+ flags.StringP(
+ "notification-slack-identifier",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"),
+ "A string which will be used to identify the messages coming from this watchtower instance")
+
+ flags.StringP(
+ "notification-slack-channel",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"),
+ "A string which overrides the webhook's default channel. Example: #my-custom-channel")
+
+ flags.StringP(
+ "notification-slack-icon-emoji",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"),
+ "An emoji code string to use in place of the default icon")
+
+ flags.StringP(
+ "notification-slack-icon-url",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"),
+ "An icon image URL string to use in place of the default icon")
+
+ flags.StringP(
+ "notification-msteams-hook",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
+ "The MSTeams WebHook URL to send notifications to")
+
+ flags.BoolP(
+ "notification-msteams-data",
+ "",
+ viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
+ "The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
+}
+
+func SetDefaults() {
+ viper.AutomaticEnv()
+ viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
+ viper.SetDefault("WATCHTOWER_POLL_INTERVAL", 300)
+ viper.SetDefault("WATCHTOWER_TIMEOUT", time.Second*10)
+ viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{})
+ viper.SetDefault("WATCHTOWER_NOTIFICATIONS_LEVEL", "info")
+ viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT", 25)
+ viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
+}
+
+// envConfig translates the command-line options into environment variables
+// that will initialize the api client
+func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
+ var err error
+ var host string
+ var tls bool
+
+ flags := cmd.PersistentFlags()
+
+ if host, err = flags.GetString("host"); err != nil {
+ return err
+ }
+ if tls, err = flags.GetBool("tlsverify"); err != nil {
+ return err
+ }
+ if err = setEnvOptStr("DOCKER_HOST", host); err != nil {
+ return err
+ }
+ if err = setEnvOptBool("DOCKER_TLS_VERIFY", tls); err != nil {
+ return err
+ }
+ if err = setEnvOptStr("DOCKER_API_VERSION", dockerAPIMinVersion); err != nil {
+ return err
+ }
+ return nil
+}
+
+func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) {
+ flags := cmd.PersistentFlags()
+
+ var err error
+ var cleanup bool
+ var noRestart bool
+ var monitorOnly bool
+ var timeout time.Duration
+
+ if cleanup, err = flags.GetBool("cleanup"); err != nil {
+ log.Fatal(err)
+ }
+ if noRestart, err = flags.GetBool("no-restart"); err != nil {
+ log.Fatal(err)
+ }
+ if monitorOnly, err = flags.GetBool("monitor-only"); err != nil {
+ log.Fatal(err)
+ }
+ if timeout, err = flags.GetDuration("stop-timeout"); err != nil {
+ log.Fatal(err)
+ }
+
+ return cleanup, noRestart, monitorOnly, timeout
+}
+
+
+func setEnvOptStr(env string, opt string) error {
+ if opt == "" || opt == os.Getenv(env) {
+ return nil
+ }
+ err := os.Setenv(env, opt)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func setEnvOptBool(env string, opt bool) error {
+ if opt == true {
+ return setEnvOptStr(env, "1")
+ }
+ return nil
+}
+
diff --git a/main.go b/main.go
index faf35b6..c2ed833 100644
--- a/main.go
+++ b/main.go
@@ -1,213 +1,21 @@
package main // import "github.com/containrrr/watchtower"
import (
- "os"
- "os/signal"
- "syscall"
- "time"
-
- "strconv"
-
- "github.com/containrrr/watchtower/actions"
- cliApp "github.com/containrrr/watchtower/app"
- "github.com/containrrr/watchtower/container"
- "github.com/containrrr/watchtower/notifications"
- "github.com/robfig/cron"
+ "github.com/containrrr/watchtower/cmd"
log "github.com/sirupsen/logrus"
- "github.com/urfave/cli"
)
// DockerAPIMinVersion is the version of the docker API, which is minimally required by
// watchtower. Currently we require at least API 1.24 and therefore Docker 1.12 or later.
-const DockerAPIMinVersion string = "1.24"
var version = "master"
var commit = "unknown"
var date = "unknown"
-var (
- client container.Client
- scheduleSpec string
- cleanup bool
- noRestart bool
- monitorOnly bool
- enableLabel bool
- notifier *notifications.Notifier
- timeout time.Duration
-)
-
func init() {
log.SetLevel(log.InfoLevel)
}
func main() {
- app := cli.NewApp()
- InitApp(app)
- cliApp.SetupCliFlags(app)
-
- if err := app.Run(os.Args); err != nil {
- log.Fatal(err)
- }
-}
-
-// InitApp initializes urfave app metadata and sets up entrypoints
-func InitApp(app *cli.App) {
- app.Name = "watchtower"
- app.Version = version + " - " + commit + " - " + date
- app.Usage = "Automatically update running Docker containers"
- app.Before = before
- app.Action = start
-}
-
-func before(c *cli.Context) error {
- if c.GlobalBool("debug") {
- log.SetLevel(log.DebugLevel)
- }
-
- pollingSet := c.IsSet("interval")
- cronSet := c.IsSet("schedule")
- cronLen := len(c.String("schedule"))
-
- if pollingSet && cronSet && cronLen > 0 {
- log.Fatal("Only schedule or interval can be defined, not both.")
- } else if cronSet && cronLen > 0 {
- scheduleSpec = c.String("schedule")
- } else {
- scheduleSpec = "@every " + strconv.Itoa(c.Int("interval")) + "s"
- }
-
- readFlags(c)
-
- if timeout < 0 {
- log.Fatal("Please specify a positive value for timeout value.")
- }
- enableLabel = c.GlobalBool("label-enable")
-
- // configure environment vars for client
- err := envConfig(c)
- if err != nil {
- return err
- }
-
- client = container.NewClient(
- !c.GlobalBool("no-pull"),
- c.GlobalBool("include-stopped"),
- )
- notifier = notifications.NewNotifier(c)
-
- return nil
-}
-
-func start(c *cli.Context) error {
- names := c.Args()
- filter := container.BuildFilter(names, enableLabel)
-
- if c.GlobalBool("run-once") {
- log.Info("Running a one time update.")
- runUpdatesWithNotifications(filter)
- os.Exit(1)
- return nil
- }
-
- if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup); err != nil {
- log.Fatal(err)
- }
-
- runUpgradesOnSchedule(filter)
- os.Exit(1)
- return nil
-}
-
-func runUpgradesOnSchedule(filter container.Filter) error {
- tryLockSem := make(chan bool, 1)
- tryLockSem <- true
-
- cron := cron.New()
- err := cron.AddFunc(
- scheduleSpec,
- func() {
- select {
- case v := <-tryLockSem:
- defer func() { tryLockSem <- v }()
- runUpdatesWithNotifications(filter)
- default:
- log.Debug("Skipped another update already running.")
- }
-
- nextRuns := cron.Entries()
- if len(nextRuns) > 0 {
- log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
- }
- })
-
- if err != nil {
- return err
- }
-
- log.Debug("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
- cron.Start()
-
- // Graceful shut-down on SIGINT/SIGTERM
- interrupt := make(chan os.Signal, 1)
- signal.Notify(interrupt, os.Interrupt)
- signal.Notify(interrupt, syscall.SIGTERM)
-
- <-interrupt
- cron.Stop()
- log.Info("Waiting for running update to be finished...")
- <-tryLockSem
- return nil
-}
-
-func runUpdatesWithNotifications(filter container.Filter) {
- notifier.StartNotification()
- updateParams := actions.UpdateParams{
- Filter: filter,
- Cleanup: cleanup,
- NoRestart: noRestart,
- Timeout: timeout,
- MonitorOnly: monitorOnly,
- }
- err := actions.Update(client, updateParams)
- if err != nil {
- log.Println(err)
- }
- notifier.SendNotification()
-}
-
-func setEnvOptStr(env string, opt string) error {
- if opt == "" || opt == os.Getenv(env) {
- return nil
- }
- err := os.Setenv(env, opt)
- if err != nil {
- return err
- }
- return nil
-}
-
-func setEnvOptBool(env string, opt bool) error {
- if opt == true {
- return setEnvOptStr(env, "1")
- }
- return nil
-}
-
-// envConfig translates the command-line options into environment variables
-// that will initialize the api client
-func envConfig(c *cli.Context) error {
- var err error
-
- err = setEnvOptStr("DOCKER_HOST", c.GlobalString("host"))
- err = setEnvOptBool("DOCKER_TLS_VERIFY", c.GlobalBool("tlsverify"))
- err = setEnvOptStr("DOCKER_API_VERSION", DockerAPIMinVersion)
-
- return err
-}
-
-func readFlags(c *cli.Context) {
- cleanup = c.GlobalBool("cleanup")
- noRestart = c.GlobalBool("no-restart")
- monitorOnly = c.GlobalBool("monitor-only")
- timeout = c.GlobalDuration("stop-timeout")
+ cmd.Execute()
}
diff --git a/notifications/email.go b/notifications/email.go
index 5f84ca5..fd3f4e8 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -3,6 +3,7 @@ package notifications
import (
"encoding/base64"
"fmt"
+ "github.com/spf13/cobra"
"net/smtp"
"os"
"time"
@@ -10,7 +11,6 @@ import (
"strconv"
log "github.com/sirupsen/logrus"
- "github.com/urfave/cli"
)
const (
@@ -31,15 +31,25 @@ type emailTypeNotifier struct {
logLevels []log.Level
}
-func newEmailNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
+func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+ flags := c.PersistentFlags()
+
+ from, _ := flags.GetString("notification-email-from")
+ to, _ := flags.GetString("notification-email-to")
+ server, _ := flags.GetString("notification-email-server")
+ user, _ := flags.GetString("notification-email-server-user")
+ password, _ := flags.GetString("notification-email-server-password")
+ port, _ := flags.GetInt("notification-email-server-port")
+ tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
+
n := &emailTypeNotifier{
- From: c.GlobalString("notification-email-from"),
- To: c.GlobalString("notification-email-to"),
- Server: c.GlobalString("notification-email-server"),
- User: c.GlobalString("notification-email-server-user"),
- Password: c.GlobalString("notification-email-server-password"),
- Port: c.GlobalInt("notification-email-server-port"),
- tlsSkipVerify: c.GlobalBool("notification-email-server-tls-skip-verify"),
+ From: from,
+ To: to,
+ Server: server,
+ User: user,
+ Password: password,
+ Port: port,
+ tlsSkipVerify: tlsSkipVerify,
logLevels: acceptedLogLevels,
}
diff --git a/notifications/msteams.go b/notifications/msteams.go
index 8bb9d7a..325c452 100644
--- a/notifications/msteams.go
+++ b/notifications/msteams.go
@@ -4,10 +4,10 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "github.com/spf13/cobra"
"net/http"
log "github.com/sirupsen/logrus"
- "github.com/urfave/cli"
"io/ioutil"
)
@@ -21,17 +21,20 @@ type msTeamsTypeNotifier struct {
data bool
}
-func newMsTeamsNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
+func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
- webHookURL := c.GlobalString("notification-msteams-hook")
+ flags := cmd.PersistentFlags()
+
+ webHookURL, _ := flags.GetString("notification-msteams-hook")
if len(webHookURL) <= 0 {
log.Fatal("Required argument --notification-msteams-hook(cli) or WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL(env) is empty.")
}
+ withData, _ := flags.GetBool("notification-msteams-data")
n := &msTeamsTypeNotifier{
levels: acceptedLogLevels,
webHookURL: webHookURL,
- data: c.GlobalBool("notification-msteams-data"),
+ data: withData,
}
log.AddHook(n)
diff --git a/notifications/notifier.go b/notifications/notifier.go
index 62e8ebc..145eccd 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -3,7 +3,7 @@ package notifications
import (
"github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
- "github.com/urfave/cli"
+ "github.com/spf13/cobra"
)
type typeNotifier interface {
@@ -17,10 +17,13 @@ type Notifier struct {
}
// NewNotifier creates and returns a new Notifier, using global configuration.
-func NewNotifier(c *cli.Context) *Notifier {
+func NewNotifier(c *cobra.Command) *Notifier {
n := &Notifier{}
- logLevel, err := log.ParseLevel(c.GlobalString("notifications-level"))
+ f := c.PersistentFlags()
+
+ level, _ := f.GetString("notifications-level")
+ logLevel, err := log.ParseLevel(level)
if err != nil {
log.Fatalf("Notifications invalid log level: %s", err.Error())
}
@@ -28,7 +31,8 @@ func NewNotifier(c *cli.Context) *Notifier {
acceptedLogLevels := slackrus.LevelThreshold(logLevel)
// Parse types and create notifiers.
- types := c.GlobalStringSlice("notifications")
+ types, _ := f.GetStringSlice("notifications")
+
for _, t := range types {
var tn typeNotifier
switch t {
diff --git a/notifications/slack.go b/notifications/slack.go
index 08f8d3b..99ad1dd 100644
--- a/notifications/slack.go
+++ b/notifications/slack.go
@@ -3,7 +3,7 @@ package notifications
import (
"github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
- "github.com/urfave/cli"
+ "github.com/spf13/cobra"
)
const (
@@ -14,20 +14,27 @@ type slackTypeNotifier struct {
slackrus.SlackrusHook
}
-func newSlackNotifier(c *cli.Context, acceptedLogLevels []log.Level) typeNotifier {
+func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+ flags := c.PersistentFlags()
+
+ hookUrl, _ := flags.GetString("notification-slack-hook-url")
+ userName, _ := flags.GetString("notification-slack-identifier")
+ channel, _ := flags.GetString("notification-slack-channel")
+ emoji, _ := flags.GetString("notification-slack-icon-emoji")
+ iconUrl, _ := flags.GetString("notification-slack-icon-url")
+
n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{
- HookURL: c.GlobalString("notification-slack-hook-url"),
- Username: c.GlobalString("notification-slack-identifier"),
- Channel: c.GlobalString("notification-slack-channel"),
- IconEmoji: c.GlobalString("notification-slack-icon-emoji"),
- IconURL: c.GlobalString("notification-slack-icon-url"),
+ HookURL: hookUrl,
+ Username: userName,
+ Channel: channel,
+ IconEmoji: emoji,
+ IconURL: iconUrl,
AcceptedLevels: acceptedLogLevels,
},
}
log.AddHook(n)
-
return n
}
From 972b0b276f6fa0b80cc15e6cae2c7837f6e994a0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 23 Jun 2019 00:32:50 +0200
Subject: [PATCH 142/761] fix linter errors
---
cmd/root.go | 9 ++++++---
internal/flags/flags.go | 7 ++++++-
notifications/slack.go | 8 ++++----
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/cmd/root.go b/cmd/root.go
index 18e20ad..a3511bf 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,7 +1,6 @@
package cmd
import (
- "fmt"
"github.com/containrrr/watchtower/actions"
"github.com/containrrr/watchtower/container"
"github.com/containrrr/watchtower/internal/flags"
@@ -17,6 +16,8 @@ import (
"github.com/spf13/cobra"
)
+// DockerAPIMinVersion is the minimum version of the docker api required to
+// use watchtower
const DockerAPIMinVersion string = "1.24"
var (
@@ -48,13 +49,14 @@ func init() {
flags.RegisterNotificationFlags(rootCmd)
}
+// Execute the root func and exit in case of errors
func Execute() {
if err := rootCmd.Execute(); err != nil {
- fmt.Println(err)
- os.Exit(1)
+ log.Fatal(err)
}
}
+// PreRun is a lifecycle hook that runs before the command is executed.
func PreRun(cmd *cobra.Command, args []string) {
f := cmd.PersistentFlags()
@@ -99,6 +101,7 @@ func PreRun(cmd *cobra.Command, args []string) {
notifier = notifications.NewNotifier(cmd)
}
+// Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) {
filter := container.BuildFilter(names, enableLabel)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 236e4ba..6962b98 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -8,12 +8,14 @@ import (
"time"
)
+// RegisterDockerFlags that are used directly by the docker api client
func RegisterDockerFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags()
flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
}
+// RegisterSystemFlags that are used by watchtower to modify the program flow
func RegisterSystemFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags()
flags.IntP(
@@ -82,6 +84,7 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"Will also include created and exited containers")
}
+// RegisterNotificationFlags that are used by watchtower to send notifications
func RegisterNotificationFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags()
@@ -185,6 +188,7 @@ Should only be used for testing.
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
}
+// SetDefaults provides default values for environment variables
func SetDefaults() {
viper.AutomaticEnv()
viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
@@ -196,7 +200,7 @@ func SetDefaults() {
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
}
-// envConfig translates the command-line options into environment variables
+// EnvConfig translates the command-line options into environment variables
// that will initialize the api client
func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
var err error
@@ -223,6 +227,7 @@ func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
return nil
}
+// ReadFlags reads common flags used in the main program flow of watchtower
func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) {
flags := cmd.PersistentFlags()
diff --git a/notifications/slack.go b/notifications/slack.go
index 99ad1dd..709c9ba 100644
--- a/notifications/slack.go
+++ b/notifications/slack.go
@@ -17,19 +17,19 @@ type slackTypeNotifier struct {
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
flags := c.PersistentFlags()
- hookUrl, _ := flags.GetString("notification-slack-hook-url")
+ hookURL, _ := flags.GetString("notification-slack-hook-url")
userName, _ := flags.GetString("notification-slack-identifier")
channel, _ := flags.GetString("notification-slack-channel")
emoji, _ := flags.GetString("notification-slack-icon-emoji")
- iconUrl, _ := flags.GetString("notification-slack-icon-url")
+ iconURL, _ := flags.GetString("notification-slack-icon-url")
n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{
- HookURL: hookUrl,
+ HookURL: hookURL,
Username: userName,
Channel: channel,
IconEmoji: emoji,
- IconURL: iconUrl,
+ IconURL: iconURL,
AcceptedLevels: acceptedLogLevels,
},
}
From 37069d51dffbf830957e4d209291e97af89554d9 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 26 Jun 2019 13:37:42 +0200
Subject: [PATCH 143/761] resolve pip issue
---
.circleci/config.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5b134e5..04c6376 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -224,7 +224,7 @@ jobs:
- run:
name: Install prerequisites
command: |
- pip install \
+ sudo pip install \
mkdocs \
mkdocs-material \
md-toc
From 061e9585345cb7f2c75595a056ab81c10b3dd50f Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 11:30:56 +0200
Subject: [PATCH 144/761] fix port typing issue introduced in 998e805
---
internal/flags/flags.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 6962b98..0915141 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -118,10 +118,10 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
"SMTP server to send notification emails through")
- flags.StringP(
+ flags.IntP(
"notification-email-server-port",
"",
- viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
+ viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
"SMTP server port to send notification emails through")
flags.BoolP(
From 76fdabfe6d2b89b8f37aa1562e28dabb63fb039b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 11:35:54 +0200
Subject: [PATCH 145/761] add githubs fingerprint to known host for mkdocs
---
.circleci/config.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 04c6376..2dafff3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -230,4 +230,6 @@ jobs:
md-toc
- run:
name: Generate and publish
- command: mkdocs gh-deploy
+ command: |
+ ssh-keyscan -H github.com >> ~/.ssh/known_hosts && \
+ mkdocs gh-deploy
From 79bf0bda56bfd8adf4de6351051693c0adeaa440 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 11:56:54 +0200
Subject: [PATCH 146/761] inject ssh key during publish
---
.circleci/config.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2dafff3..3a4f752 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -60,7 +60,8 @@ workflows:
- linting
filters:
branches:
- ignore: /.*/
+ only: /master/
+ # ignore: /.*/
tags:
only: /^v[0-9]+(\.[0-9]+)*$/
jobs:
@@ -228,8 +229,12 @@ jobs:
mkdocs \
mkdocs-material \
md-toc
+ - add_ssh_keys:
+ fingerprints:
+ - "89:6c:8d:6a:c2:7d:68:d6:4e:e3:4d:ca:00:f2:c2:59"
- run:
name: Generate and publish
command: |
+ mkdir ~/.ssh && touch ~/.ssh/known_hosts;
ssh-keyscan -H github.com >> ~/.ssh/known_hosts && \
mkdocs gh-deploy
From 0e268a247ee5ca8fec83ae82f7967a8dbf1d409f Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 12:13:46 +0200
Subject: [PATCH 147/761] change fingerprint
---
.circleci/config.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3a4f752..5a8b2b6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -231,7 +231,7 @@ jobs:
md-toc
- add_ssh_keys:
fingerprints:
- - "89:6c:8d:6a:c2:7d:68:d6:4e:e3:4d:ca:00:f2:c2:59"
+ - "91:75:47:15:b2:8e:85:e5:67:0e:63:7f:22:d2:b4:6e"
- run:
name: Generate and publish
command: |
From b4846ef1580946f64c670014f3e046bcbb4d1caa Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 12:17:15 +0200
Subject: [PATCH 148/761] turn on publish filter again
---
.circleci/config.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5a8b2b6..6b35d35 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -60,8 +60,7 @@ workflows:
- linting
filters:
branches:
- only: /master/
- # ignore: /.*/
+ ignore: /.*/
tags:
only: /^v[0-9]+(\.[0-9]+)*$/
jobs:
From 9eca883f17669bc93e0f1dc4b9be95b7f3684656 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 2 Jul 2019 18:24:19 +0200
Subject: [PATCH 149/761] fix: remove unnecessary cronSet check
resolves issue 343
---
cmd/root.go | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/cmd/root.go b/cmd/root.go
index a3511bf..2c1bdff 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -65,13 +65,12 @@ func PreRun(cmd *cobra.Command, args []string) {
}
pollingSet := f.Changed("interval")
- cronSet := f.Changed("schedule")
schedule, _ := f.GetString("schedule")
cronLen := len(schedule)
- if pollingSet && cronSet && cronLen > 0 {
+ if pollingSet && cronLen > 0 {
log.Fatal("Only schedule or interval can be defined, not both.")
- } else if cronSet && cronLen > 0 {
+ } else if cronLen > 0 {
scheduleSpec, _ = f.GetString("schedule")
} else {
interval, _ := f.GetInt("interval")
From baf5e50051f0dc4c4d6dac77ae1503cacb51fdcf Mon Sep 17 00:00:00 2001
From: Zois Pagoulatos
Date: Sun, 21 Jul 2019 18:00:56 +0200
Subject: [PATCH 150/761] Re-apply based on new go flags package (#336)
---
actions/actions_suite_test.go | 24 ++++++++++++++----------
actions/update.go | 8 ++++----
cmd/root.go | 21 +++++++++++----------
container/client.go | 13 ++++++++-----
docs/arguments.md | 10 ++++++++++
go.sum | 2 ++
internal/flags/flags.go | 14 +++++++++-----
7 files changed, 58 insertions(+), 34 deletions(-)
diff --git a/actions/actions_suite_test.go b/actions/actions_suite_test.go
index 850fd4e..fcbe461 100644
--- a/actions/actions_suite_test.go
+++ b/actions/actions_suite_test.go
@@ -32,9 +32,10 @@ var _ = Describe("the actions package", func() {
})
BeforeEach(func() {
client = mockClient{
- api: dockerClient,
- pullImages: false,
- TestData: &TestData{},
+ api: dockerClient,
+ pullImages: false,
+ removeVolumes: false,
+ TestData: &TestData{},
}
})
@@ -62,8 +63,9 @@ var _ = Describe("the actions package", func() {
When("given multiple containers", func() {
BeforeEach(func() {
client = mockClient{
- api: dockerClient,
- pullImages: false,
+ api: dockerClient,
+ pullImages: false,
+ removeVolumes: false,
TestData: &TestData{
NameOfContainerToKeep: "test-container-02",
Containers: []container.Container{
@@ -89,8 +91,9 @@ var _ = Describe("the actions package", func() {
When("deciding whether to cleanup images", func() {
BeforeEach(func() {
client = mockClient{
- api: dockerClient,
- pullImages: false,
+ api: dockerClient,
+ pullImages: false,
+ removeVolumes: false,
TestData: &TestData{
Containers: []container.Container{
createMockContainer(
@@ -134,9 +137,10 @@ func createMockContainer(id string, name string, image string, created time.Time
}
type mockClient struct {
- TestData *TestData
- api cli.CommonAPIClient
- pullImages bool
+ TestData *TestData
+ api cli.CommonAPIClient
+ pullImages bool
+ removeVolumes bool
}
type TestData struct {
diff --git a/actions/update.go b/actions/update.go
index a18ea4c..a288729 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -14,10 +14,10 @@ var (
// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
- Filter container.Filter
- Cleanup bool
- NoRestart bool
- Timeout time.Duration
+ Filter container.Filter
+ Cleanup bool
+ NoRestart bool
+ Timeout time.Duration
MonitorOnly bool
}
diff --git a/cmd/root.go b/cmd/root.go
index 2c1bdff..a64a045 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,17 +1,18 @@
package cmd
import (
+ "os"
+ "os/signal"
+ "strconv"
+ "syscall"
+ "time"
+
"github.com/containrrr/watchtower/actions"
"github.com/containrrr/watchtower/container"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/notifications"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
- "os"
- "os/signal"
- "strconv"
- "syscall"
- "time"
"github.com/spf13/cobra"
)
@@ -32,9 +33,9 @@ var (
)
var rootCmd = &cobra.Command{
- Use: "watchtower",
- Short: "Automatically updates running Docker containers",
- Long: `
+ Use: "watchtower",
+ Short: "Automatically updates running Docker containers",
+ Long: `
Watchtower automatically updates running Docker containers whenever a new image is released.
More information available at https://github.com/containrrr/watchtower/.
`,
@@ -92,9 +93,11 @@ func PreRun(cmd *cobra.Command, args []string) {
noPull, _ := f.GetBool("no-pull")
includeStopped, _ := f.GetBool("include-stopped")
+ removeVolumes, _ := f.GetBool("remove-volumes")
client = container.NewClient(
!noPull,
includeStopped,
+ removeVolumes,
)
notifier = notifications.NewNotifier(cmd)
@@ -176,5 +179,3 @@ func runUpdatesWithNotifications(filter container.Filter) {
}
notifier.SendNotification()
}
-
-
diff --git a/container/client.go b/container/client.go
index 70a6fb1..b707d0c 100644
--- a/container/client.go
+++ b/container/client.go
@@ -2,11 +2,12 @@ package container
import (
"fmt"
- "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/filters"
"io/ioutil"
"time"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client"
@@ -35,7 +36,7 @@ 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, includeStopped bool) Client {
+func NewClient(pullImages bool, includeStopped bool, removeVolumes bool) Client {
cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
if err != nil {
@@ -45,6 +46,7 @@ func NewClient(pullImages bool, includeStopped bool) Client {
return dockerClient{
api: cli,
pullImages: pullImages,
+ removeVolumes: removeVolumes,
includeStopped: includeStopped,
}
}
@@ -52,6 +54,7 @@ func NewClient(pullImages bool, includeStopped bool) Client {
type dockerClient struct {
api dockerclient.CommonAPIClient
pullImages bool
+ removeVolumes bool
includeStopped bool
}
@@ -71,7 +74,7 @@ func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
types.ContainerListOptions{
Filters: filter,
})
-
+
if err != nil {
return nil, err
}
@@ -131,7 +134,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
} else {
log.Debugf("Removing container %s", c.ID())
- if err := client.api.ContainerRemove(bg, c.ID(), types.ContainerRemoveOptions{Force: true, RemoveVolumes: false}); err != nil {
+ if err := client.api.ContainerRemove(bg, c.ID(), types.ContainerRemoveOptions{Force: true, RemoveVolumes: client.removeVolumes}); err != nil {
return err
}
}
diff --git a/docs/arguments.md b/docs/arguments.md
index b62fd50..3631476 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -47,6 +47,16 @@ Environment Variable: WATCHTOWER_CLEANUP
Default: false
```
+## Remove attached volumes
+Removes attached volumes after updating. When this flag is specified, watchtower will remove all attached volumes from the container before restarting container with a new image. Use this option to force new volumes to be populated as containers are updated.
+
+```
+ Argument: --remove-volumes
+Environment Variable: WATCHTOWER_REMOVE_VOLUMES
+ Type: Boolean
+ Default: false
+```
+
## Debug
Enable debug mode with verbose logging.
diff --git a/go.sum b/go.sum
index c9968f6..2107c46 100644
--- a/go.sum
+++ b/go.sum
@@ -68,6 +68,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -258,5 +259,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 0915141..d6b0cb1 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -1,11 +1,12 @@
package flags
import (
+ "os"
+ "time"
+
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
- "os"
- "time"
)
// RegisterDockerFlags that are used directly by the docker api client
@@ -52,6 +53,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetBool("WATCHTOWER_CLEANUP"),
"remove previously used images after updating")
+ flags.BoolP(
+ "remove-volumes",
+ "",
+ viper.GetBool("WATCHTOWER_REMOVE_VOLUMES"),
+ "remove attached volumes before updating")
+
flags.BoolP(
"label-enable",
"e",
@@ -64,7 +71,6 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetBool("WATCHTOWER_DEBUG"),
"enable debug mode with verbose logging")
-
flags.BoolP(
"monitor-only",
"m",
@@ -253,7 +259,6 @@ func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) {
return cleanup, noRestart, monitorOnly, timeout
}
-
func setEnvOptStr(env string, opt string) error {
if opt == "" || opt == os.Getenv(env) {
return nil
@@ -271,4 +276,3 @@ func setEnvOptBool(env string, opt bool) error {
}
return nil
}
-
From e109a7a6cec3d4165bd17a8f39c3305f8ec965fe Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 21 Jul 2019 19:58:19 +0200
Subject: [PATCH 151/761] refactor: extract types and pkgs to new files
---
actions/actions_suite_test.go | 3 ++-
actions/update.go | 3 ++-
cmd/root.go | 5 ++--
container/client.go | 8 +++---
container/container.go | 11 +++++----
container/filters.go | 30 ++++++++---------------
{container => internal/util}/util.go | 10 ++++----
{container => internal/util}/util_test.go | 16 ++++++------
notifications/email.go | 6 ++---
notifications/msteams.go | 3 ++-
notifications/notifier.go | 10 +++-----
notifications/slack.go | 3 ++-
pkg/types/filter.go | 5 ++++
pkg/types/filterable_container.go | 9 +++++++
pkg/types/notifier.go | 6 +++++
15 files changed, 71 insertions(+), 57 deletions(-)
rename {container => internal/util}/util.go (72%)
rename {container => internal/util}/util_test.go (86%)
create mode 100644 pkg/types/filter.go
create mode 100644 pkg/types/filterable_container.go
create mode 100644 pkg/types/notifier.go
diff --git a/actions/actions_suite_test.go b/actions/actions_suite_test.go
index fcbe461..0afab92 100644
--- a/actions/actions_suite_test.go
+++ b/actions/actions_suite_test.go
@@ -10,6 +10,7 @@ import (
"github.com/containrrr/watchtower/container/mocks"
"github.com/docker/docker/api/types"
+ t "github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client"
. "github.com/onsi/ginkgo"
@@ -149,7 +150,7 @@ type TestData struct {
Containers []container.Container
}
-func (client mockClient) ListContainers(f container.Filter) ([]container.Container, error) {
+func (client mockClient) ListContainers(f t.Filter) ([]container.Container, error) {
return client.TestData.Containers, nil
}
diff --git a/actions/update.go b/actions/update.go
index a288729..8bfa14b 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/containrrr/watchtower/container"
+ t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
@@ -14,7 +15,7 @@ var (
// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
- Filter container.Filter
+ Filter t.Filter
Cleanup bool
NoRestart bool
Timeout time.Duration
diff --git a/cmd/root.go b/cmd/root.go
index a64a045..2514a0d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -11,6 +11,7 @@ import (
"github.com/containrrr/watchtower/container"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/notifications"
+ t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
@@ -123,7 +124,7 @@ func Run(c *cobra.Command, names []string) {
os.Exit(1)
}
-func runUpgradesOnSchedule(filter container.Filter) error {
+func runUpgradesOnSchedule(filter t.Filter) error {
tryLockSem := make(chan bool, 1)
tryLockSem <- true
@@ -164,7 +165,7 @@ func runUpgradesOnSchedule(filter container.Filter) error {
return nil
}
-func runUpdatesWithNotifications(filter container.Filter) {
+func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification()
updateParams := actions.UpdateParams{
Filter: filter,
diff --git a/container/client.go b/container/client.go
index b707d0c..b6abdd9 100644
--- a/container/client.go
+++ b/container/client.go
@@ -5,10 +5,10 @@ import (
"io/ioutil"
"time"
+ t "github.com/containrrr/watchtower/pkg/types"
+ "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
-
- "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
@@ -22,7 +22,7 @@ const (
// A Client is the interface through which watchtower interacts with the
// Docker API.
type Client interface {
- ListContainers(Filter) ([]Container, error)
+ ListContainers(t.Filter) ([]Container, error)
StopContainer(Container, time.Duration) error
StartContainer(Container) error
RenameContainer(Container, string) error
@@ -58,7 +58,7 @@ type dockerClient struct {
includeStopped bool
}
-func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
+func (client dockerClient) ListContainers(fn t.Filter) ([]Container, error) {
cs := []Container{}
bg := context.Background()
diff --git a/container/container.go b/container/container.go
index 66ae505..14b0e86 100644
--- a/container/container.go
+++ b/container/container.go
@@ -2,6 +2,7 @@ package container
import (
"fmt"
+ "github.com/containrrr/watchtower/internal/util"
"strconv"
"strings"
@@ -146,19 +147,19 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
config.User = ""
}
- if sliceEqual(config.Cmd, imageConfig.Cmd) {
+ if util.SliceEqual(config.Cmd, imageConfig.Cmd) {
config.Cmd = nil
}
- if sliceEqual(config.Entrypoint, imageConfig.Entrypoint) {
+ if util.SliceEqual(config.Entrypoint, imageConfig.Entrypoint) {
config.Entrypoint = nil
}
- config.Env = sliceSubtract(config.Env, imageConfig.Env)
+ config.Env = util.SliceSubtract(config.Env, imageConfig.Env)
- config.Labels = stringMapSubtract(config.Labels, imageConfig.Labels)
+ config.Labels = util.StringMapSubtract(config.Labels, imageConfig.Labels)
- config.Volumes = structMapSubtract(config.Volumes, imageConfig.Volumes)
+ config.Volumes = util.StructMapSubtract(config.Volumes, imageConfig.Volumes)
// subtract ports exposed in image from container
for k := range config.ExposedPorts {
diff --git a/container/filters.go b/container/filters.go
index 1b3fbbd..b4d4911 100644
--- a/container/filters.go
+++ b/container/filters.go
@@ -1,30 +1,20 @@
package container
-// A Filter is a prototype for a function that can be used to filter the
-// results from a call to the ListContainers() method on the Client.
-type Filter func(FilterableContainer) bool
-
-// A FilterableContainer is the interface which is used to filter
-// containers.
-type FilterableContainer interface {
- Name() string
- IsWatchtower() bool
- Enabled() (bool, bool)
-}
+import t "github.com/containrrr/watchtower/pkg/types"
// WatchtowerContainersFilter filters only watchtower containers
-func WatchtowerContainersFilter(c FilterableContainer) bool { return c.IsWatchtower() }
+func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() }
// Filter no containers and returns all
-func noFilter(FilterableContainer) bool { return true }
+func noFilter(t.FilterableContainer) bool { return true }
// Filters containers which don't have a specified name
-func filterByNames(names []string, baseFilter Filter) Filter {
+func filterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 {
return baseFilter
}
- return func(c FilterableContainer) bool {
+ return func(c t.FilterableContainer) bool {
for _, name := range names {
if (name == c.Name()) || (name == c.Name()[1:]) {
return baseFilter(c)
@@ -35,8 +25,8 @@ func filterByNames(names []string, baseFilter Filter) Filter {
}
// Filters out containers that don't have the 'enableLabel'
-func filterByEnableLabel(baseFilter Filter) Filter {
- return func(c FilterableContainer) bool {
+func filterByEnableLabel(baseFilter t.Filter) t.Filter {
+ return func(c t.FilterableContainer) bool {
// If label filtering is enabled, containers should only be considered
// if the label is specifically set.
_, ok := c.Enabled()
@@ -49,8 +39,8 @@ func filterByEnableLabel(baseFilter Filter) Filter {
}
// Filters out containers that have a 'enableLabel' and is set to disable.
-func filterByDisabledLabel(baseFilter Filter) Filter {
- return func(c FilterableContainer) bool {
+func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
+ return func(c t.FilterableContainer) bool {
enabledLabel, ok := c.Enabled()
if ok && !enabledLabel {
// If the label has been set and it demands a disable
@@ -62,7 +52,7 @@ func filterByDisabledLabel(baseFilter Filter) Filter {
}
// BuildFilter creates the needed filter of containers
-func BuildFilter(names []string, enableLabel bool) Filter {
+func BuildFilter(names []string, enableLabel bool) t.Filter {
filter := noFilter
filter = filterByNames(names, filter)
if enableLabel {
diff --git a/container/util.go b/internal/util/util.go
similarity index 72%
rename from container/util.go
rename to internal/util/util.go
index 3db01d1..924e9c2 100644
--- a/container/util.go
+++ b/internal/util/util.go
@@ -1,6 +1,6 @@
-package container
+package util
-func sliceEqual(s1, s2 []string) bool {
+func SliceEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
@@ -14,7 +14,7 @@ func sliceEqual(s1, s2 []string) bool {
return true
}
-func sliceSubtract(a1, a2 []string) []string {
+func SliceSubtract(a1, a2 []string) []string {
a := []string{}
for _, e1 := range a1 {
@@ -35,7 +35,7 @@ func sliceSubtract(a1, a2 []string) []string {
return a
}
-func stringMapSubtract(m1, m2 map[string]string) map[string]string {
+func StringMapSubtract(m1, m2 map[string]string) map[string]string {
m := map[string]string{}
for k1, v1 := range m1 {
@@ -51,7 +51,7 @@ func stringMapSubtract(m1, m2 map[string]string) map[string]string {
return m
}
-func structMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
+func StructMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
m := map[string]struct{}{}
for k1, v1 := range m1 {
diff --git a/container/util_test.go b/internal/util/util_test.go
similarity index 86%
rename from container/util_test.go
rename to internal/util/util_test.go
index 8c4eef9..e5bf6ba 100644
--- a/container/util_test.go
+++ b/internal/util/util_test.go
@@ -1,8 +1,8 @@
-package container
+package util
import (
- "testing"
"github.com/stretchr/testify/assert"
+ "testing"
)
@@ -11,7 +11,7 @@ func TestSliceEqual_True(t *testing.T) {
s1 := []string{"a", "b", "c"}
s2 := []string{"a", "b", "c"}
- result := sliceEqual(s1, s2)
+ result := SliceEqual(s1, s2)
assert.True(t, result)
}
@@ -20,7 +20,7 @@ func TestSliceEqual_DifferentLengths(t *testing.T) {
s1 := []string{"a", "b", "c"}
s2 := []string{"a", "b", "c", "d"}
- result := sliceEqual(s1, s2)
+ result := SliceEqual(s1, s2)
assert.False(t, result)
}
@@ -29,7 +29,7 @@ func TestSliceEqual_DifferentContents(t *testing.T) {
s1 := []string{"a", "b", "c"}
s2 := []string{"a", "b", "d"}
- result := sliceEqual(s1, s2)
+ result := SliceEqual(s1, s2)
assert.False(t, result)
}
@@ -38,7 +38,7 @@ func TestSliceSubtract(t *testing.T) {
a1 := []string{"a", "b", "c"}
a2 := []string{"a", "c"}
- result := sliceSubtract(a1, a2)
+ result := SliceSubtract(a1, a2)
assert.Equal(t, []string{"b"}, result)
assert.Equal(t, []string{"a", "b", "c"}, a1)
assert.Equal(t, []string{"a", "c"}, a2)
@@ -48,7 +48,7 @@ func TestStringMapSubtract(t *testing.T) {
m1 := map[string]string{"a": "a", "b": "b", "c": "sea"}
m2 := map[string]string{"a": "a", "c": "c"}
- result := stringMapSubtract(m1, m2)
+ result := StringMapSubtract(m1, m2)
assert.Equal(t, map[string]string{"b": "b", "c": "sea"}, result)
assert.Equal(t, map[string]string{"a": "a", "b": "b", "c": "sea"}, m1)
assert.Equal(t, map[string]string{"a": "a", "c": "c"}, m2)
@@ -59,7 +59,7 @@ func TestStructMapSubtract(t *testing.T) {
m1 := map[string]struct{}{"a": x, "b": x, "c": x}
m2 := map[string]struct{}{"a": x, "c": x}
- result := structMapSubtract(m1, m2)
+ result := StructMapSubtract(m1, m2)
assert.Equal(t, map[string]struct{}{"b": x}, result)
assert.Equal(t, map[string]struct{}{"a": x, "b": x, "c": x}, m1)
assert.Equal(t, map[string]struct{}{"a": x, "c": x}, m2)
diff --git a/notifications/email.go b/notifications/email.go
index fd3f4e8..8a0ebfe 100644
--- a/notifications/email.go
+++ b/notifications/email.go
@@ -9,7 +9,7 @@ import (
"time"
"strconv"
-
+ t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
@@ -17,7 +17,7 @@ const (
emailType = "email"
)
-// Implements typeNotifier, logrus.Hook
+// Implements Notifier, logrus.Hook
// The default logrus email integration would have several issues:
// - It would send one email per log output
// - It would only send errors
@@ -31,7 +31,7 @@ type emailTypeNotifier struct {
logLevels []log.Level
}
-func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
from, _ := flags.GetString("notification-email-from")
diff --git a/notifications/msteams.go b/notifications/msteams.go
index 325c452..b356814 100644
--- a/notifications/msteams.go
+++ b/notifications/msteams.go
@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"net/http"
+ t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
"io/ioutil"
)
@@ -21,7 +22,7 @@ type msTeamsTypeNotifier struct {
data bool
}
-func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := cmd.PersistentFlags()
diff --git a/notifications/notifier.go b/notifications/notifier.go
index 145eccd..b14ba24 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -1,19 +1,17 @@
package notifications
import (
+ ty "github.com/containrrr/watchtower/pkg/types"
"github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
-type typeNotifier interface {
- StartNotification()
- SendNotification()
-}
+
// Notifier can send log output as notification to admins, with optional batching.
type Notifier struct {
- types []typeNotifier
+ types []ty.Notifier
}
// NewNotifier creates and returns a new Notifier, using global configuration.
@@ -34,7 +32,7 @@ func NewNotifier(c *cobra.Command) *Notifier {
types, _ := f.GetStringSlice("notifications")
for _, t := range types {
- var tn typeNotifier
+ var tn ty.Notifier
switch t {
case emailType:
tn = newEmailNotifier(c, acceptedLogLevels)
diff --git a/notifications/slack.go b/notifications/slack.go
index 709c9ba..0bc8ae1 100644
--- a/notifications/slack.go
+++ b/notifications/slack.go
@@ -4,6 +4,7 @@ import (
"github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ t "github.com/containrrr/watchtower/pkg/types"
)
const (
@@ -14,7 +15,7 @@ type slackTypeNotifier struct {
slackrus.SlackrusHook
}
-func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
hookURL, _ := flags.GetString("notification-slack-hook-url")
diff --git a/pkg/types/filter.go b/pkg/types/filter.go
new file mode 100644
index 0000000..514e4bd
--- /dev/null
+++ b/pkg/types/filter.go
@@ -0,0 +1,5 @@
+package types
+
+// A Filter is a prototype for a function that can be used to filter the
+// results from a call to the ListContainers() method on the Client.
+type Filter func(FilterableContainer) bool
diff --git a/pkg/types/filterable_container.go b/pkg/types/filterable_container.go
new file mode 100644
index 0000000..d89b910
--- /dev/null
+++ b/pkg/types/filterable_container.go
@@ -0,0 +1,9 @@
+package types
+
+// A FilterableContainer is the interface which is used to filter
+// containers.
+type FilterableContainer interface {
+ Name() string
+ IsWatchtower() bool
+ Enabled() (bool, bool)
+}
diff --git a/pkg/types/notifier.go b/pkg/types/notifier.go
new file mode 100644
index 0000000..f073552
--- /dev/null
+++ b/pkg/types/notifier.go
@@ -0,0 +1,6 @@
+package types
+
+type Notifier interface {
+ StartNotification()
+ SendNotification()
+}
From 74ce92760c41f555f3f1e2d0e28778c5101351d0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 21 Jul 2019 20:14:28 +0200
Subject: [PATCH 152/761] refactor: move container into pkg
---
actions/actions_suite_test.go | 4 ++--
actions/check.go | 2 +-
actions/update.go | 2 +-
cmd/root.go | 2 +-
{container => pkg/container}/client.go | 0
{container => pkg/container}/container.go | 0
{container => pkg/container}/container_test.go | 2 +-
{container => pkg/container}/filters.go | 0
{container => pkg/container}/filters_test.go | 2 +-
{container => pkg/container}/mocks/ApiServer.go | 0
{container => pkg/container}/mocks/FilterableContainer.go | 0
.../container}/mocks/data/container_running.json | 0
.../container}/mocks/data/container_stopped.json | 0
{container => pkg/container}/mocks/data/containers.json | 0
{container => pkg/container}/mocks/data/image01.json | 0
{container => pkg/container}/mocks/data/image02.json | 0
{container => pkg/container}/sort.go | 0
{container => pkg/container}/trust.go | 0
{container => pkg/container}/trust_test.go | 0
19 files changed, 7 insertions(+), 7 deletions(-)
rename {container => pkg/container}/client.go (100%)
rename {container => pkg/container}/container.go (100%)
rename {container => pkg/container}/container_test.go (99%)
rename {container => pkg/container}/filters.go (100%)
rename {container => pkg/container}/filters_test.go (98%)
rename {container => pkg/container}/mocks/ApiServer.go (100%)
rename {container => pkg/container}/mocks/FilterableContainer.go (100%)
rename {container => pkg/container}/mocks/data/container_running.json (100%)
rename {container => pkg/container}/mocks/data/container_stopped.json (100%)
rename {container => pkg/container}/mocks/data/containers.json (100%)
rename {container => pkg/container}/mocks/data/image01.json (100%)
rename {container => pkg/container}/mocks/data/image02.json (100%)
rename {container => pkg/container}/sort.go (100%)
rename {container => pkg/container}/trust.go (100%)
rename {container => pkg/container}/trust_test.go (100%)
diff --git a/actions/actions_suite_test.go b/actions/actions_suite_test.go
index 0afab92..93fd417 100644
--- a/actions/actions_suite_test.go
+++ b/actions/actions_suite_test.go
@@ -6,8 +6,8 @@ import (
"time"
"github.com/containrrr/watchtower/actions"
- "github.com/containrrr/watchtower/container"
- "github.com/containrrr/watchtower/container/mocks"
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/docker/docker/api/types"
t "github.com/containrrr/watchtower/pkg/types"
diff --git a/actions/check.go b/actions/check.go
index 16fd42e..df052c8 100644
--- a/actions/check.go
+++ b/actions/check.go
@@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus"
- "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/pkg/container"
)
// CheckForMultipleWatchtowerInstances will ensure that there are not multiple instances of the
diff --git a/actions/update.go b/actions/update.go
index 8bfa14b..6827687 100644
--- a/actions/update.go
+++ b/actions/update.go
@@ -4,7 +4,7 @@ import (
"math/rand"
"time"
- "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/pkg/container"
t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
diff --git a/cmd/root.go b/cmd/root.go
index 2514a0d..22a0d6d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -8,7 +8,7 @@ import (
"time"
"github.com/containrrr/watchtower/actions"
- "github.com/containrrr/watchtower/container"
+ "github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/notifications"
t "github.com/containrrr/watchtower/pkg/types"
diff --git a/container/client.go b/pkg/container/client.go
similarity index 100%
rename from container/client.go
rename to pkg/container/client.go
diff --git a/container/container.go b/pkg/container/container.go
similarity index 100%
rename from container/container.go
rename to pkg/container/container.go
diff --git a/container/container_test.go b/pkg/container/container_test.go
similarity index 99%
rename from container/container_test.go
rename to pkg/container/container_test.go
index f9dd540..0cb8931 100644
--- a/container/container_test.go
+++ b/pkg/container/container_test.go
@@ -1,7 +1,7 @@
package container
import (
- "github.com/containrrr/watchtower/container/mocks"
+ "github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
cli "github.com/docker/docker/client"
diff --git a/container/filters.go b/pkg/container/filters.go
similarity index 100%
rename from container/filters.go
rename to pkg/container/filters.go
diff --git a/container/filters_test.go b/pkg/container/filters_test.go
similarity index 98%
rename from container/filters_test.go
rename to pkg/container/filters_test.go
index 0db0a62..00ebae9 100644
--- a/container/filters_test.go
+++ b/pkg/container/filters_test.go
@@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/containrrr/watchtower/container/mocks"
+ "github.com/containrrr/watchtower/pkg/container/mocks"
)
func TestWatchtowerContainersFilter(t *testing.T) {
diff --git a/container/mocks/ApiServer.go b/pkg/container/mocks/ApiServer.go
similarity index 100%
rename from container/mocks/ApiServer.go
rename to pkg/container/mocks/ApiServer.go
diff --git a/container/mocks/FilterableContainer.go b/pkg/container/mocks/FilterableContainer.go
similarity index 100%
rename from container/mocks/FilterableContainer.go
rename to pkg/container/mocks/FilterableContainer.go
diff --git a/container/mocks/data/container_running.json b/pkg/container/mocks/data/container_running.json
similarity index 100%
rename from container/mocks/data/container_running.json
rename to pkg/container/mocks/data/container_running.json
diff --git a/container/mocks/data/container_stopped.json b/pkg/container/mocks/data/container_stopped.json
similarity index 100%
rename from container/mocks/data/container_stopped.json
rename to pkg/container/mocks/data/container_stopped.json
diff --git a/container/mocks/data/containers.json b/pkg/container/mocks/data/containers.json
similarity index 100%
rename from container/mocks/data/containers.json
rename to pkg/container/mocks/data/containers.json
diff --git a/container/mocks/data/image01.json b/pkg/container/mocks/data/image01.json
similarity index 100%
rename from container/mocks/data/image01.json
rename to pkg/container/mocks/data/image01.json
diff --git a/container/mocks/data/image02.json b/pkg/container/mocks/data/image02.json
similarity index 100%
rename from container/mocks/data/image02.json
rename to pkg/container/mocks/data/image02.json
diff --git a/container/sort.go b/pkg/container/sort.go
similarity index 100%
rename from container/sort.go
rename to pkg/container/sort.go
diff --git a/container/trust.go b/pkg/container/trust.go
similarity index 100%
rename from container/trust.go
rename to pkg/container/trust.go
diff --git a/container/trust_test.go b/pkg/container/trust_test.go
similarity index 100%
rename from container/trust_test.go
rename to pkg/container/trust_test.go
From 62f603bb258dde3c1d95ed5b0fa5c6acb1296561 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 21 Jul 2019 20:15:04 +0200
Subject: [PATCH 153/761] refactor: move actions into pkg
---
cmd/root.go | 4 ++--
{actions => pkg/actions}/actions_suite_test.go | 11 +++++------
{actions => pkg/actions}/check.go | 0
{actions => pkg/actions}/update.go | 0
4 files changed, 7 insertions(+), 8 deletions(-)
rename {actions => pkg/actions}/actions_suite_test.go (91%)
rename {actions => pkg/actions}/check.go (100%)
rename {actions => pkg/actions}/update.go (100%)
diff --git a/cmd/root.go b/cmd/root.go
index 22a0d6d..10023f4 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -7,10 +7,10 @@ import (
"syscall"
"time"
- "github.com/containrrr/watchtower/actions"
- "github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/notifications"
+ "github.com/containrrr/watchtower/pkg/actions"
+ "github.com/containrrr/watchtower/pkg/container"
t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
diff --git a/actions/actions_suite_test.go b/pkg/actions/actions_suite_test.go
similarity index 91%
rename from actions/actions_suite_test.go
rename to pkg/actions/actions_suite_test.go
index 93fd417..c0e07f7 100644
--- a/actions/actions_suite_test.go
+++ b/pkg/actions/actions_suite_test.go
@@ -5,7 +5,6 @@ import (
"testing"
"time"
- "github.com/containrrr/watchtower/actions"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/docker/docker/api/types"
@@ -44,7 +43,7 @@ var _ = Describe("the actions package", func() {
When("given an empty array", func() {
It("should not do anything", func() {
client.TestData.Containers = []container.Container{}
- err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ err := CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -57,7 +56,7 @@ var _ = Describe("the actions package", func() {
"watchtower",
time.Now()),
}
- err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ err := CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -85,7 +84,7 @@ var _ = Describe("the actions package", func() {
}
})
It("should stop all but the latest one", func() {
- err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ err := CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -112,12 +111,12 @@ var _ = Describe("the actions package", func() {
}
})
It("should try to delete the image if the cleanup flag is true", func() {
- err := actions.CheckForMultipleWatchtowerInstances(client, true)
+ err := CheckForMultipleWatchtowerInstances(client, true)
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImage).To(BeTrue())
})
It("should not try to delete the image if the cleanup flag is false", func() {
- err := actions.CheckForMultipleWatchtowerInstances(client, false)
+ err := CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImage).To(BeFalse())
})
diff --git a/actions/check.go b/pkg/actions/check.go
similarity index 100%
rename from actions/check.go
rename to pkg/actions/check.go
diff --git a/actions/update.go b/pkg/actions/update.go
similarity index 100%
rename from actions/update.go
rename to pkg/actions/update.go
From a425bf102423493d3e9bb71121267a8518573e5b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 21 Jul 2019 22:22:30 +0200
Subject: [PATCH 154/761] refactor: move actions into internal
---
cmd/root.go | 4 ++--
{pkg => internal}/actions/actions_suite_test.go | 11 ++++++-----
{pkg => internal}/actions/check.go | 0
{pkg => internal}/actions/update.go | 0
internal/util/util.go | 4 ++++
internal/util/util_test.go | 2 --
main.go | 2 +-
pkg/container/container_test.go | 3 +--
pkg/container/filters_test.go | 2 +-
pkg/container/trust_test.go | 6 +-----
{notifications => pkg/notifications}/email.go | 8 ++++----
{notifications => pkg/notifications}/msteams.go | 0
{notifications => pkg/notifications}/notifier.go | 2 --
{notifications => pkg/notifications}/slack.go | 10 +++++-----
{notifications => pkg/notifications}/smtp.go | 0
{notifications => pkg/notifications}/util.go | 0
pkg/types/notifier.go | 1 +
17 files changed, 26 insertions(+), 29 deletions(-)
rename {pkg => internal}/actions/actions_suite_test.go (91%)
rename {pkg => internal}/actions/check.go (100%)
rename {pkg => internal}/actions/update.go (100%)
rename {notifications => pkg/notifications}/email.go (96%)
rename {notifications => pkg/notifications}/msteams.go (100%)
rename {notifications => pkg/notifications}/notifier.go (99%)
rename {notifications => pkg/notifications}/slack.go (76%)
rename {notifications => pkg/notifications}/smtp.go (100%)
rename {notifications => pkg/notifications}/util.go (100%)
diff --git a/cmd/root.go b/cmd/root.go
index 10023f4..195b174 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -7,10 +7,10 @@ import (
"syscall"
"time"
+ "github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/internal/flags"
- "github.com/containrrr/watchtower/notifications"
- "github.com/containrrr/watchtower/pkg/actions"
"github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/notifications"
t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
diff --git a/pkg/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
similarity index 91%
rename from pkg/actions/actions_suite_test.go
rename to internal/actions/actions_suite_test.go
index c0e07f7..031f54c 100644
--- a/pkg/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -2,6 +2,7 @@ package actions_test
import (
"errors"
+ "github.com/containrrr/watchtower/internal/actions"
"testing"
"time"
@@ -43,7 +44,7 @@ var _ = Describe("the actions package", func() {
When("given an empty array", func() {
It("should not do anything", func() {
client.TestData.Containers = []container.Container{}
- err := CheckForMultipleWatchtowerInstances(client, false)
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -56,7 +57,7 @@ var _ = Describe("the actions package", func() {
"watchtower",
time.Now()),
}
- err := CheckForMultipleWatchtowerInstances(client, false)
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -84,7 +85,7 @@ var _ = Describe("the actions package", func() {
}
})
It("should stop all but the latest one", func() {
- err := CheckForMultipleWatchtowerInstances(client, false)
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
})
})
@@ -111,12 +112,12 @@ var _ = Describe("the actions package", func() {
}
})
It("should try to delete the image if the cleanup flag is true", func() {
- err := CheckForMultipleWatchtowerInstances(client, true)
+ err := actions.CheckForMultipleWatchtowerInstances(client, true)
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImage).To(BeTrue())
})
It("should not try to delete the image if the cleanup flag is false", func() {
- err := CheckForMultipleWatchtowerInstances(client, false)
+ err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImage).To(BeFalse())
})
diff --git a/pkg/actions/check.go b/internal/actions/check.go
similarity index 100%
rename from pkg/actions/check.go
rename to internal/actions/check.go
diff --git a/pkg/actions/update.go b/internal/actions/update.go
similarity index 100%
rename from pkg/actions/update.go
rename to internal/actions/update.go
diff --git a/internal/util/util.go b/internal/util/util.go
index 924e9c2..08c88bc 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -1,5 +1,6 @@
package util
+// SliceEqual compares two slices and checks whether they have equal content
func SliceEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
@@ -14,6 +15,7 @@ func SliceEqual(s1, s2 []string) bool {
return true
}
+// SliceSubtract subtracts the content of slice a2 from slice a1
func SliceSubtract(a1, a2 []string) []string {
a := []string{}
@@ -35,6 +37,7 @@ func SliceSubtract(a1, a2 []string) []string {
return a
}
+// StringMapSubtract subtracts the content of structmap m2 from structmap m1
func StringMapSubtract(m1, m2 map[string]string) map[string]string {
m := map[string]string{}
@@ -51,6 +54,7 @@ func StringMapSubtract(m1, m2 map[string]string) map[string]string {
return m
}
+// StructMapSubtract subtracts the content of structmap m2 from structmap m1
func StructMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
m := map[string]struct{}{}
diff --git a/internal/util/util_test.go b/internal/util/util_test.go
index e5bf6ba..a6dd657 100644
--- a/internal/util/util_test.go
+++ b/internal/util/util_test.go
@@ -5,8 +5,6 @@ import (
"testing"
)
-
-
func TestSliceEqual_True(t *testing.T) {
s1 := []string{"a", "b", "c"}
s2 := []string{"a", "b", "c"}
diff --git a/main.go b/main.go
index c2ed833..193e249 100644
--- a/main.go
+++ b/main.go
@@ -17,5 +17,5 @@ func init() {
}
func main() {
- cmd.Execute()
+ cmd.Execute()
}
diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go
index 0cb8931..9e1b213 100644
--- a/pkg/container/container_test.go
+++ b/pkg/container/container_test.go
@@ -23,8 +23,7 @@ var _ = Describe("the container", func() {
server := mocks.NewMockAPIServer()
docker, _ = cli.NewClientWithOpts(
cli.WithHost(server.URL),
- cli.WithHTTPClient(server.Client(),
- ))
+ cli.WithHTTPClient(server.Client()))
client = dockerClient{
api: docker,
pullImages: false,
diff --git a/pkg/container/filters_test.go b/pkg/container/filters_test.go
index 00ebae9..4118335 100644
--- a/pkg/container/filters_test.go
+++ b/pkg/container/filters_test.go
@@ -3,8 +3,8 @@ package container
import (
"testing"
- "github.com/stretchr/testify/assert"
"github.com/containrrr/watchtower/pkg/container/mocks"
+ "github.com/stretchr/testify/assert"
)
func TestWatchtowerContainersFilter(t *testing.T) {
diff --git a/pkg/container/trust_test.go b/pkg/container/trust_test.go
index 6aa807b..7d2ac96 100644
--- a/pkg/container/trust_test.go
+++ b/pkg/container/trust_test.go
@@ -1,15 +1,11 @@
package container
import (
+ "github.com/stretchr/testify/assert"
"os"
"testing"
- "github.com/stretchr/testify/assert"
)
-
-
-
-
func TestEncodedEnvAuth_ShouldReturnAnErrorIfRepoEnvsAreUnset(t *testing.T) {
os.Unsetenv("REPO_USER")
os.Unsetenv("REPO_PASS")
diff --git a/notifications/email.go b/pkg/notifications/email.go
similarity index 96%
rename from notifications/email.go
rename to pkg/notifications/email.go
index 8a0ebfe..60db9cb 100644
--- a/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -8,9 +8,9 @@ import (
"os"
"time"
- "strconv"
t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
+ "strconv"
)
const (
@@ -35,8 +35,8 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
flags := c.PersistentFlags()
from, _ := flags.GetString("notification-email-from")
- to, _ := flags.GetString("notification-email-to")
- server, _ := flags.GetString("notification-email-server")
+ to, _ := flags.GetString("notification-email-to")
+ server, _ := flags.GetString("notification-email-server")
user, _ := flags.GetString("notification-email-server-user")
password, _ := flags.GetString("notification-email-server-password")
port, _ := flags.GetInt("notification-email-server-port")
@@ -70,7 +70,7 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
}
t := time.Now()
-
+
header := make(map[string]string)
header["From"] = e.From
header["To"] = e.To
diff --git a/notifications/msteams.go b/pkg/notifications/msteams.go
similarity index 100%
rename from notifications/msteams.go
rename to pkg/notifications/msteams.go
diff --git a/notifications/notifier.go b/pkg/notifications/notifier.go
similarity index 99%
rename from notifications/notifier.go
rename to pkg/notifications/notifier.go
index b14ba24..f077c7f 100644
--- a/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -7,8 +7,6 @@ import (
"github.com/spf13/cobra"
)
-
-
// Notifier can send log output as notification to admins, with optional batching.
type Notifier struct {
types []ty.Notifier
diff --git a/notifications/slack.go b/pkg/notifications/slack.go
similarity index 76%
rename from notifications/slack.go
rename to pkg/notifications/slack.go
index 0bc8ae1..42b7915 100644
--- a/notifications/slack.go
+++ b/pkg/notifications/slack.go
@@ -1,10 +1,10 @@
package notifications
import (
+ t "github.com/containrrr/watchtower/pkg/types"
"github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
- t "github.com/containrrr/watchtower/pkg/types"
)
const (
@@ -18,11 +18,11 @@ type slackTypeNotifier struct {
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
- hookURL, _ := flags.GetString("notification-slack-hook-url")
+ hookURL, _ := flags.GetString("notification-slack-hook-url")
userName, _ := flags.GetString("notification-slack-identifier")
- channel, _ := flags.GetString("notification-slack-channel")
- emoji, _ := flags.GetString("notification-slack-icon-emoji")
- iconURL, _ := flags.GetString("notification-slack-icon-url")
+ channel, _ := flags.GetString("notification-slack-channel")
+ emoji, _ := flags.GetString("notification-slack-icon-emoji")
+ iconURL, _ := flags.GetString("notification-slack-icon-url")
n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{
diff --git a/notifications/smtp.go b/pkg/notifications/smtp.go
similarity index 100%
rename from notifications/smtp.go
rename to pkg/notifications/smtp.go
diff --git a/notifications/util.go b/pkg/notifications/util.go
similarity index 100%
rename from notifications/util.go
rename to pkg/notifications/util.go
diff --git a/pkg/types/notifier.go b/pkg/types/notifier.go
index f073552..c8d07d0 100644
--- a/pkg/types/notifier.go
+++ b/pkg/types/notifier.go
@@ -1,5 +1,6 @@
package types
+// Notifier is the interface that all notification services have in common
type Notifier interface {
StartNotification()
SendNotification()
From 6c507433e8b9f5fc264861c81dd83cf605bbd651 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 22 Jul 2019 10:20:11 +0200
Subject: [PATCH 155/761] refactor: split out more code into separate files
---
internal/actions/update.go | 125 ++++++++++++++----------------
internal/actions/update_params.go | 15 ++++
internal/util/rand_name.go | 15 ++++
3 files changed, 87 insertions(+), 68 deletions(-)
create mode 100644 internal/actions/update_params.go
create mode 100644 internal/util/rand_name.go
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 6827687..a3cf928 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -1,27 +1,11 @@
package actions
import (
- "math/rand"
- "time"
-
+ "github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
- t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
-var (
- letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-)
-
-// UpdateParams contains all different options available to alter the behavior of the Update func
-type UpdateParams struct {
- Filter t.Filter
- Cleanup bool
- NoRestart bool
- Timeout time.Duration
- MonitorOnly bool
-}
-
// Update looks at the running Docker containers to see if any of the images
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
@@ -55,51 +39,66 @@ func Update(client container.Client, params UpdateParams) error {
return nil
}
- // Stop stale containers in reverse order
- for i := len(containers) - 1; i >= 0; i-- {
- container := containers[i]
-
- if container.IsWatchtower() {
- log.Debugf("This is the watchtower container %s", containers[i].Name())
- continue
- }
-
- if container.Stale {
- if err := client.StopContainer(container, params.Timeout); err != nil {
- log.Error(err)
- }
- }
- }
-
- // Restart stale containers in sorted order
- for _, container := range containers {
- if container.Stale {
- // Since we can't shutdown a watchtower container immediately, we need to
- // start the new one while the old one is still running. This prevents us
- // from re-using the same container name so we first rename the current
- // instance so that the new one can adopt the old name.
- if container.IsWatchtower() {
- if err := client.RenameContainer(container, randName()); err != nil {
- log.Error(err)
- continue
- }
- }
-
- if !params.NoRestart {
- if err := client.StartContainer(container); err != nil {
- log.Error(err)
- }
- }
-
- if params.Cleanup {
- client.RemoveImage(container)
- }
- }
- }
+ stopContainersInReversedOrder(containers, client, params)
+ restartContainersInSortedOrder(containers, client, params)
return nil
}
+func stopContainersInReversedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+ for i := len(containers) - 1; i >= 0; i-- {
+ stopStaleContainer(containers[i], client, params)
+ }
+}
+
+func stopStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+ if container.IsWatchtower() {
+ log.Debugf("This is the watchtower container %s", container.Name())
+ return
+ }
+
+ if !container.Stale {
+ return
+ }
+
+ err := client.StopContainer(container, params.Timeout)
+ if err != nil {
+ log.Error(err)
+ }
+}
+
+func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+ for _, container := range containers {
+ if !container.Stale {
+ continue
+ }
+ restartStaleContainer(container, client, params)
+ }
+}
+
+func restartStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+ // Since we can't shutdown a watchtower container immediately, we need to
+ // start the new one while the old one is still running. This prevents us
+ // from re-using the same container name so we first rename the current
+ // instance so that the new one can adopt the old name.
+ if container.IsWatchtower() {
+ if err := client.RenameContainer(container, util.RandName()); err != nil {
+ log.Error(err)
+ return
+ }
+ }
+
+ if !params.NoRestart {
+ if err := client.StartContainer(container); err != nil {
+ log.Error(err)
+ }
+ }
+
+ if params.Cleanup {
+ client.RemoveImage(container)
+ }
+}
+
func checkDependencies(containers []container.Container) {
for i, parent := range containers {
@@ -118,13 +117,3 @@ func checkDependencies(containers []container.Container) {
}
}
}
-
-// Generates a random, 32-character, Docker-compatible container name.
-func randName() string {
- b := make([]rune, 32)
- for i := range b {
- b[i] = letters[rand.Intn(len(letters))]
- }
-
- return string(b)
-}
diff --git a/internal/actions/update_params.go b/internal/actions/update_params.go
new file mode 100644
index 0000000..851f23e
--- /dev/null
+++ b/internal/actions/update_params.go
@@ -0,0 +1,15 @@
+package actions
+
+import (
+ t "github.com/containrrr/watchtower/pkg/types"
+ "time"
+)
+
+// UpdateParams contains all different options available to alter the behavior of the Update func
+type UpdateParams struct {
+ Filter t.Filter
+ Cleanup bool
+ NoRestart bool
+ Timeout time.Duration
+ MonitorOnly bool
+}
diff --git a/internal/util/rand_name.go b/internal/util/rand_name.go
new file mode 100644
index 0000000..76f6a3f
--- /dev/null
+++ b/internal/util/rand_name.go
@@ -0,0 +1,15 @@
+package util
+
+import "math/rand"
+
+var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+// RandName Generates a random, 32-character, Docker-compatible container name.
+func RandName() string {
+ b := make([]rune, 32)
+ for i := range b {
+ b[i] = letters[rand.Intn(len(letters))]
+ }
+
+ return string(b)
+}
From e4e1127f8e8ae728cd1e87c270c9c590d765fe8a Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 22 Jul 2019 12:10:57 +0200
Subject: [PATCH 156/761] fix: remove linting issues
---
cmd/root.go | 7 +++++--
internal/actions/check.go | 2 +-
internal/actions/update.go | 4 +++-
internal/flags/flags.go | 2 +-
main.go | 9 +--------
pkg/container/client.go | 12 +++++++-----
pkg/container/trust.go | 7 ++++++-
7 files changed, 24 insertions(+), 19 deletions(-)
diff --git a/cmd/root.go b/cmd/root.go
index 195b174..a162c6e 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -62,7 +62,7 @@ func Execute() {
func PreRun(cmd *cobra.Command, args []string) {
f := cmd.PersistentFlags()
- if enabled, _ := f.GetBool("debug"); enabled == true {
+ if enabled, _ := f.GetBool("debug"); enabled {
log.SetLevel(log.DebugLevel)
}
@@ -120,7 +120,10 @@ func Run(c *cobra.Command, names []string) {
log.Fatal(err)
}
- runUpgradesOnSchedule(filter)
+ if err := runUpgradesOnSchedule(filter); err != nil {
+ log.Error(err)
+ }
+
os.Exit(1)
}
diff --git a/internal/actions/check.go b/internal/actions/check.go
index df052c8..704d2d8 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -50,7 +50,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
continue
}
- if cleanup == true {
+ if cleanup {
if err := client.RemoveImage(c); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
diff --git a/internal/actions/update.go b/internal/actions/update.go
index a3cf928..e3be205 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -95,7 +95,9 @@ func restartStaleContainer(container container.Container, client container.Clien
}
if params.Cleanup {
- client.RemoveImage(container)
+ if err := client.RemoveImage(container); err != nil {
+ log.Error(err)
+ }
}
}
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index d6b0cb1..645dcac 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -271,7 +271,7 @@ func setEnvOptStr(env string, opt string) error {
}
func setEnvOptBool(env string, opt bool) error {
- if opt == true {
+ if opt {
return setEnvOptStr(env, "1")
}
return nil
diff --git a/main.go b/main.go
index 193e249..9f8a012 100644
--- a/main.go
+++ b/main.go
@@ -1,17 +1,10 @@
-package main // import "github.com/containrrr/watchtower"
+package main
import (
"github.com/containrrr/watchtower/cmd"
log "github.com/sirupsen/logrus"
)
-// DockerAPIMinVersion is the version of the docker API, which is minimally required by
-// watchtower. Currently we require at least API 1.24 and therefore Docker 1.12 or later.
-
-var version = "master"
-var commit = "unknown"
-var date = "unknown"
-
func init() {
log.SetLevel(log.InfoLevel)
}
diff --git a/pkg/container/client.go b/pkg/container/client.go
index b6abdd9..2aa6bc0 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -126,8 +126,8 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
}
}
- // Wait for container to exit, but proceed anyway after the timeout elapses
- client.waitForStop(c, timeout)
+ // TODO: This should probably be checked.
+ _ = client.waitForStopOrTimeout(c, timeout)
if c.containerInfo.HostConfig.AutoRemove {
log.Debugf("AutoRemove container %s, skipping ContainerRemove call.", c.ID())
@@ -140,7 +140,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
}
// Wait for container to be removed. In this case an error is a good thing
- if err := client.waitForStop(c, timeout); err == nil {
+ if err := client.waitForStopOrTimeout(c, timeout); err == nil {
return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
}
@@ -245,7 +245,9 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
defer response.Close()
// the pull request will be aborted prematurely unless the response is read
- _, err = ioutil.ReadAll(response)
+ if _, err = ioutil.ReadAll(response); err != nil {
+ log.Error(err)
+ }
}
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
@@ -269,7 +271,7 @@ func (client dockerClient) RemoveImage(c Container) error {
return err
}
-func (client dockerClient) waitForStop(c Container, waitTime time.Duration) error {
+func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Duration) error {
bg := context.Background()
timeout := time.After(waitTime)
diff --git a/pkg/container/trust.go b/pkg/container/trust.go
index 92ab696..63b76a6 100644
--- a/pkg/container/trust.go
+++ b/pkg/container/trust.go
@@ -48,6 +48,10 @@ func EncodedEnvAuth(ref string) (string, error) {
// The docker config must be mounted on the container
func EncodedConfigAuth(ref string) (string, error) {
server, err := ParseServerAddress(ref)
+ if err != nil {
+ log.Errorf("Unable to parse the image ref %s", err)
+ return "", err
+ }
configDir := os.Getenv("DOCKER_CONFIG")
if configDir == "" {
configDir = "/"
@@ -58,7 +62,8 @@ func EncodedConfigAuth(ref string) (string, error) {
return "", err
}
credStore := CredentialsStore(*configFile)
- auth, err := credStore.Get(server) // returns (types.AuthConfig{}) if server not in credStore
+ auth, _ := credStore.Get(server) // returns (types.AuthConfig{}) if server not in credStore
+
if auth == (types.AuthConfig{}) {
log.Debugf("No credentials for %s in %s", server, configFile.Filename)
return "", nil
From d744b5ddf7c122e3603f450ad9d87cc5d25ff62d Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Mon, 22 Jul 2019 12:29:46 +0200
Subject: [PATCH 157/761] docs: add lukapeschke as a contributor (#350)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 10 ++++++++++
README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f57ce74..ebd7824 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -293,6 +293,16 @@
"doc"
]
},
+ {
+ "login": "lukapeschke",
+ "name": "Luka Peschke",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/17085536?v=4",
+ "profile": "https://github.com/lukapeschke",
+ "contributions": [
+ "code",
+ "doc"
+ ]
+ }
{
"login": "zoispag",
"name": "Zois Pagoulatos",
diff --git a/README.md b/README.md
index 0b34aec..ed8fb56 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,51 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-
+
From dff16dc639797496920a274b76d2aeb63683cb5f Mon Sep 17 00:00:00 2001
From: Luka Peschke
Date: Mon, 22 Jul 2019 21:17:54 +0200
Subject: [PATCH 158/761] Add support for Gotify notifications (#346)
This adds support for Gotify (https://gotify.net) notifications.
Work items:
* Two flags have been added to internal/flags/flags.go:
"notification-gotify-url" and "notification-gotify-token".
* A Gotify notification driver has been added in
notifications/gotify.go.
* "gotify" has been added to notification driver choices in
notifications/notifier.go.
* Docs have been updated
---
docs/notifications.md | 18 +++++-
internal/flags/flags.go | 13 ++++-
notifications/gotify.go | 100 ++++++++++++++++++++++++++++++++++
pkg/notifications/notifier.go | 2 +
4 files changed, 131 insertions(+), 2 deletions(-)
create mode 100644 notifications/gotify.go
diff --git a/docs/notifications.md b/docs/notifications.md
index 02f144a..853ee63 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -1,4 +1,4 @@
-
+
# Notifications
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
@@ -7,6 +7,7 @@ The types of notifications to send are passed via the comma-separated option `--
- `email` to send notifications via e-mail
- `slack` to send notifications through a Slack webhook
- `msteams` to send notifications via MSTeams webhook
+- `gotify` to send notifications via Gotify
## Settings
@@ -90,3 +91,18 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
containrrr/watchtower
```
+
+### Gotify
+
+To push a notification to your Gotify instance, register a Gotify app and specify the Gotify URL and app token:
+
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=gotify \
+ -e WATCHTOWER_NOTIFICATION_GOTIFY_URL="https://my.gotify.tld/" \
+ -e WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN="SuperSecretToken" \
+ containrrr/watchtower
+```
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 645dcac..ae786cd 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -98,7 +98,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"notifications",
"n",
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
- " notification types to send (valid: email, slack, msteams")
+ " notification types to send (valid: email, slack, msteams, gotify)")
flags.StringP(
"notifications-level",
@@ -192,6 +192,17 @@ Should only be used for testing.
"",
viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
+
+ flags.StringP(
+ "notification-gotify-url",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
+ "The Gotify URL to send notifications to")
+ flags.StringP(
+ "notification-gotify-token",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
+ "The Gotify Application required to query the Gotify API")
}
// SetDefaults provides default values for environment variables
diff --git a/notifications/gotify.go b/notifications/gotify.go
new file mode 100644
index 0000000..733bddd
--- /dev/null
+++ b/notifications/gotify.go
@@ -0,0 +1,100 @@
+package notifications
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+const (
+ gotifyType = "gotify"
+)
+
+type gotifyTypeNotifier struct {
+ gotifyURL string
+ gotifyAppToken string
+ logLevels []log.Level
+}
+
+func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+ flags := c.PersistentFlags()
+
+ gotifyURL, _ := flags.GetString("notification-gotify-url")
+ if len(gotifyURL) < 1 {
+ log.Fatal("Required argument --notification-gotify-url(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_URL(env) is empty.")
+ } else if !(strings.HasPrefix(gotifyURL, "http://") || strings.HasPrefix(gotifyURL, "https://")) {
+ log.Fatal("Gotify URL must start with \"http://\" or \"https://\"")
+ } else if strings.HasPrefix(gotifyURL, "http://") {
+ log.Warn("Using an HTTP url fpr Gotify is insecure")
+ }
+
+ gotifyToken, _ := flags.GetString("notification-gotify-token")
+ if len(gotifyToken) < 1 {
+ log.Fatal("Required argument --notification-gotify-token(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN(env) is empty.")
+ }
+
+ n := &gotifyTypeNotifier{
+ gotifyURL: gotifyURL,
+ gotifyAppToken: gotifyToken,
+ logLevels: acceptedLogLevels,
+ }
+
+ log.AddHook(n)
+
+ return n
+}
+
+func (n *gotifyTypeNotifier) StartNotification() {}
+
+func (n *gotifyTypeNotifier) SendNotification() {}
+
+func (n *gotifyTypeNotifier) Levels() []log.Level {
+ return n.logLevels
+}
+
+func (n *gotifyTypeNotifier) getURL() string {
+ url := n.gotifyURL
+ if !strings.HasSuffix(url, "/") {
+ url += "/"
+ }
+ return url + "message?token=" + n.gotifyAppToken
+}
+
+func (n *gotifyTypeNotifier) Fire(entry *log.Entry) error {
+
+ go func() {
+ jsonBody, err := json.Marshal(gotifyMessage{
+ Message: "(" + entry.Level.String() + "): " + entry.Message,
+ Title: "Watchtower",
+ Priority: 0,
+ })
+ if err != nil {
+ fmt.Println("Failed to create JSON body for Gotify notification: ", err)
+ return
+ }
+
+ jsonBodyBuffer := bytes.NewBuffer([]byte(jsonBody))
+ resp, err := http.Post(n.getURL(), "application/json", jsonBodyBuffer)
+ if err != nil {
+ fmt.Println("Failed to send Gotify notification: ", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ fmt.Printf("Gotify notification returned %d HTTP status code", resp.StatusCode)
+ }
+
+ }()
+ return nil
+}
+
+type gotifyMessage struct {
+ Message string `json:"message"`
+ Title string `json:"title"`
+ Priority int `json:"priority"`
+}
diff --git a/pkg/notifications/notifier.go b/pkg/notifications/notifier.go
index f077c7f..2f25824 100644
--- a/pkg/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -38,6 +38,8 @@ func NewNotifier(c *cobra.Command) *Notifier {
tn = newSlackNotifier(c, acceptedLogLevels)
case msTeamsType:
tn = newMsTeamsNotifier(c, acceptedLogLevels)
+ case gotifyType:
+ tn = newGotifyNotifier(c, acceptedLogLevels)
default:
log.Fatalf("Unknown notification type %q", t)
}
From 874180a51878b737527d12936612b7a83cf5489a Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 23 Jul 2019 09:36:04 +0200
Subject: [PATCH 159/761] fix: resolve merge issues
---
pkg/container/client.go | 2 +-
{notifications => pkg/notifications}/gotify.go | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
rename {notifications => pkg/notifications}/gotify.go (97%)
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 2aa6bc0..124e889 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -247,7 +247,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
// the pull request will be aborted prematurely unless the response is read
if _, err = ioutil.ReadAll(response); err != nil {
log.Error(err)
- }
+ }
}
newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
diff --git a/notifications/gotify.go b/pkg/notifications/gotify.go
similarity index 97%
rename from notifications/gotify.go
rename to pkg/notifications/gotify.go
index 733bddd..47ea884 100644
--- a/notifications/gotify.go
+++ b/pkg/notifications/gotify.go
@@ -7,6 +7,7 @@ import (
"net/http"
"strings"
+ t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -21,7 +22,7 @@ type gotifyTypeNotifier struct {
logLevels []log.Level
}
-func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) typeNotifier {
+func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
gotifyURL, _ := flags.GetString("notification-gotify-url")
From bfae38dbf8b8aaa3108a61e7c2570d97faa28c7c Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 27 Jul 2019 01:37:16 +0200
Subject: [PATCH 160/761] Feat/lifecycle hooks (#351)
* feat(update): add lifecycle hooks to the update action
* fix(ci): add bash tests for lifecycle-hooks to the ci workflow
* fix(ci): move integration tests to an isolated step
* fix(ci): fix malformed all-contributors json
* fix(ci): disable automatic bash test until we figure out a reasonable way to run it in circleci
---
.all-contributorsrc | 2 +-
.circleci/config.yml | 17 ++
cmd/root.go | 31 ++--
docs/lifecycle-hooks.md | 45 ++++++
internal/actions/actions_suite_test.go | 10 +-
internal/actions/update.go | 46 +++++-
internal/actions/update_params.go | 11 +-
internal/flags/flags.go | 6 +
pkg/container/client.go | 124 ++++++++++++---
pkg/container/container.go | 33 ++--
pkg/container/metadata.go | 39 +++++
scripts/lifecycle-tests.sh | 208 +++++++++++++++++++++++++
12 files changed, 499 insertions(+), 73 deletions(-)
create mode 100644 docs/lifecycle-hooks.md
create mode 100644 pkg/container/metadata.go
create mode 100755 scripts/lifecycle-tests.sh
diff --git a/.all-contributorsrc b/.all-contributorsrc
index ebd7824..d41fdf2 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -302,7 +302,7 @@
"code",
"doc"
]
- }
+ },
{
"login": "zoispag",
"name": "Zois Pagoulatos",
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6b35d35..c6b93d1 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -36,9 +36,18 @@ workflows:
only: /.*/
tags:
only: /.*/
+ # - integration_testing:
+ # requires:
+ # - checkout
+ # filters:
+ # branches:
+ # only: /.*/
+ # tags:
+ # only: /.*/
- build:
requires:
- testing
+ # - integration_testing
- linting
filters:
branches:
@@ -90,6 +99,14 @@ jobs:
- run: go get -u github.com/haya14busa/goverage
- run: goverage -v -coverprofile=coverage.out ./...
- run: godacov -t $CODACY_TOKEN -r ./coverage.out -c $CIRCLE_SHA1
+ #integration_testing:
+ # executor: go
+ # steps:
+ # - attach_workspace:
+ # at: .
+ # - run: go build .
+ # - setup_remote_docker
+ # - run: ./scripts/lifecycle-tests.sh
build:
executor: go
steps:
diff --git a/cmd/root.go b/cmd/root.go
index a162c6e..b18ba06 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -23,14 +23,15 @@ import (
const DockerAPIMinVersion string = "1.24"
var (
- client container.Client
- scheduleSpec string
- cleanup bool
- noRestart bool
- monitorOnly bool
- enableLabel bool
- notifier *notifications.Notifier
- timeout time.Duration
+ client container.Client
+ scheduleSpec string
+ cleanup bool
+ noRestart bool
+ monitorOnly bool
+ enableLabel bool
+ notifier *notifications.Notifier
+ timeout time.Duration
+ lifecycleHooks bool
)
var rootCmd = &cobra.Command{
@@ -84,7 +85,9 @@ func PreRun(cmd *cobra.Command, args []string) {
if timeout < 0 {
log.Fatal("Please specify a positive value for timeout value.")
}
+
enableLabel, _ = f.GetBool("label-enable")
+ lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
// configure environment vars for client
err := flags.EnvConfig(cmd, DockerAPIMinVersion)
@@ -95,6 +98,7 @@ func PreRun(cmd *cobra.Command, args []string) {
noPull, _ := f.GetBool("no-pull")
includeStopped, _ := f.GetBool("include-stopped")
removeVolumes, _ := f.GetBool("remove-volumes")
+
client = container.NewClient(
!noPull,
includeStopped,
@@ -171,11 +175,12 @@ func runUpgradesOnSchedule(filter t.Filter) error {
func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification()
updateParams := actions.UpdateParams{
- Filter: filter,
- Cleanup: cleanup,
- NoRestart: noRestart,
- Timeout: timeout,
- MonitorOnly: monitorOnly,
+ Filter: filter,
+ Cleanup: cleanup,
+ NoRestart: noRestart,
+ Timeout: timeout,
+ MonitorOnly: monitorOnly,
+ LifecycleHooks: lifecycleHooks,
}
err := actions.Update(client, updateParams)
if err != nil {
diff --git a/docs/lifecycle-hooks.md b/docs/lifecycle-hooks.md
new file mode 100644
index 0000000..bbf9d21
--- /dev/null
+++ b/docs/lifecycle-hooks.md
@@ -0,0 +1,45 @@
+
+## Executing commands before and after updating
+
+> **DO NOTE**: Both commands are shell commands executed with `sh`, and therefore require the
+> container to provide the `sh` executable.
+
+It is possible to execute a *pre-update* command and a *post-update* command
+**inside** every container updated by watchtower. The *pre-update* command is
+executed before stopping the container, and the *post-update* command is
+executed after restarting the container.
+
+This feature is disabled by default. To enable it, you need to set the option
+`--enable-lifecycle-hooks` on the command line, or set the environment variable
+`WATCHTOWER_LIFECYCLE_HOOKS` to true.
+
+
+
+### Specifying update commands
+
+The commands are specified using docker container labels, with
+`com.centurylinklabs.watchtower.pre-update-command` for the *pre-update*
+command and `com.centurylinklabs.watchtower.lifecycle.post-update` for the
+*post-update* command.
+
+These labels can be declared as instructions in a Dockerfile:
+
+```docker
+LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh"
+LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh"
+```
+
+Or be specified as part of the `docker run` command line:
+
+```bash
+docker run -d \
+ --label=com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" \
+ --label=com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" \
+ someimage
+```
+
+### Execution failure
+
+The failure of a command to execute, identified by an exit code different than
+0, will not prevent watchtower from updating the container. Only an error
+log statement containing the exit code will be reported.
\ No newline at end of file
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index 031f54c..76d2be5 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -160,7 +160,7 @@ func (client mockClient) StopContainer(c container.Container, d time.Duration) e
}
return nil
}
-func (client mockClient) StartContainer(c container.Container) error {
+func (client mockClient) StartContainer(c container.Container) (string, error) {
panic("Not implemented")
}
@@ -173,6 +173,14 @@ func (client mockClient) RemoveImage(c container.Container) error {
return nil
}
+func (client mockClient) GetContainer(containerID string) (container.Container, error) {
+ return container.Container{}, nil
+}
+
+func (client mockClient) ExecuteCommand(containerID string, command string) error {
+ return nil
+}
+
func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
panic("Not implemented")
}
diff --git a/internal/actions/update.go b/internal/actions/update.go
index e3be205..5763017 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -61,8 +61,9 @@ func stopStaleContainer(container container.Container, client container.Client,
return
}
- err := client.StopContainer(container, params.Timeout)
- if err != nil {
+ executePreUpdateCommand(client, container)
+
+ if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
}
@@ -89,8 +90,10 @@ func restartStaleContainer(container container.Container, client container.Clien
}
if !params.NoRestart {
- if err := client.StartContainer(container); err != nil {
+ if newContainerID, err := client.StartContainer(container); err != nil {
log.Error(err)
+ } else if container.Stale && params.LifecycleHooks {
+ executePostUpdateCommand(client, newContainerID)
}
}
@@ -104,18 +107,49 @@ func restartStaleContainer(container container.Container, client container.Clien
func checkDependencies(containers []container.Container) {
for i, parent := range containers {
- if parent.Stale {
+ if parent.ToRestart() {
continue
}
LinkLoop:
for _, linkName := range parent.Links() {
for _, child := range containers {
- if child.Name() == linkName && child.Stale {
- containers[i].Stale = true
+ if child.Name() == linkName && child.ToRestart() {
+ containers[i].Linked = true
break LinkLoop
}
}
}
}
}
+
+func executePreUpdateCommand(client container.Client, container container.Container) {
+
+ command := container.GetLifecyclePreUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-update command supplied. Skipping")
+ }
+
+ log.Info("Executing pre-update command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+func executePostUpdateCommand(client container.Client, newContainerID string) {
+ newContainer, err := client.GetContainer(newContainerID)
+ if err != nil {
+ log.Error(err)
+ return
+ }
+
+ command := newContainer.GetLifecyclePostUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No post-update command supplied. Skipping")
+ }
+
+ log.Info("Executing post-update command.")
+ if err := client.ExecuteCommand(newContainerID, command); err != nil {
+ log.Error(err)
+ }
+}
diff --git a/internal/actions/update_params.go b/internal/actions/update_params.go
index 851f23e..ff586c6 100644
--- a/internal/actions/update_params.go
+++ b/internal/actions/update_params.go
@@ -7,9 +7,10 @@ import (
// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
- Filter t.Filter
- Cleanup bool
- NoRestart bool
- Timeout time.Duration
- MonitorOnly bool
+ Filter t.Filter
+ Cleanup bool
+ NoRestart bool
+ Timeout time.Duration
+ MonitorOnly bool
+ LifecycleHooks bool
}
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index ae786cd..d416243 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -88,6 +88,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"S",
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
"Will also include created and exited containers")
+
+ flags.BoolP(
+ "enable-lifecycle-hooks",
+ "",
+ viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
+ "Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
}
// RegisterNotificationFlags that are used by watchtower to send notifications
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 124e889..0dc22db 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -1,8 +1,10 @@
package container
import (
+ "bytes"
"fmt"
"io/ioutil"
+ "strings"
"time"
t "github.com/containrrr/watchtower/pkg/types"
@@ -15,18 +17,18 @@ import (
"golang.org/x/net/context"
)
-const (
- defaultStopSignal = "SIGTERM"
-)
+const defaultStopSignal = "SIGTERM"
// A Client is the interface through which watchtower interacts with the
// Docker API.
type Client interface {
ListContainers(t.Filter) ([]Container, error)
+ GetContainer(containerID string) (Container, error)
StopContainer(Container, time.Duration) error
- StartContainer(Container) error
+ StartContainer(Container) (string, error)
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
+ ExecuteCommand(containerID string, command string) error
RemoveImage(Container) error
}
@@ -80,18 +82,12 @@ func (client dockerClient) ListContainers(fn t.Filter) ([]Container, error) {
}
for _, runningContainer := range containers {
- containerInfo, err := client.api.ContainerInspect(bg, runningContainer.ID)
+
+ c, err := client.GetContainer(runningContainer.ID)
if err != nil {
return nil, err
}
- imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
- if err != nil {
- return nil, err
- }
-
- c := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
-
if fn(c) {
cs = append(cs, c)
}
@@ -112,6 +108,23 @@ func (client dockerClient) createListFilter() filters.Args {
return filterArgs
}
+func (client dockerClient) GetContainer(containerID string) (Container, error) {
+ bg := context.Background()
+
+ containerInfo, err := client.api.ContainerInspect(bg, containerID)
+ if err != nil {
+ return Container{}, err
+ }
+
+ imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
+ if err != nil {
+ return Container{}, err
+ }
+
+ container := Container{containerInfo: &containerInfo, imageInfo: &imageInfo}
+ return container, nil
+}
+
func (client dockerClient) StopContainer(c Container, timeout time.Duration) error {
bg := context.Background()
signal := c.StopSignal()
@@ -147,7 +160,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
return nil
}
-func (client dockerClient) StartContainer(c Container) error {
+func (client dockerClient) StartContainer(c Container) (string, error) {
bg := context.Background()
config := c.runtimeConfig()
hostConfig := c.hostConfig()
@@ -167,40 +180,40 @@ func (client dockerClient) StartContainer(c Container) error {
name := c.Name()
log.Infof("Creating %s", name)
- creation, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, name)
+ createdContainer, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, name)
if err != nil {
- return err
+ return "", err
}
if !(hostConfig.NetworkMode.IsHost()) {
for k := range simpleNetworkConfig.EndpointsConfig {
- err = client.api.NetworkDisconnect(bg, k, creation.ID, true)
+ err = client.api.NetworkDisconnect(bg, k, createdContainer.ID, true)
if err != nil {
- return err
+ return "", err
}
}
for k, v := range networkConfig.EndpointsConfig {
- err = client.api.NetworkConnect(bg, k, creation.ID, v)
+ err = client.api.NetworkConnect(bg, k, createdContainer.ID, v)
if err != nil {
- return err
+ return "", err
}
}
}
- return client.startContainerIfPreviouslyRunning(bg, c, creation)
+ if !c.IsRunning() {
+ return createdContainer.ID, nil
+ }
+
+ return createdContainer.ID, client.doStartContainer(bg, c, createdContainer)
}
-func (client dockerClient) startContainerIfPreviouslyRunning(bg context.Context, c Container, creation container.ContainerCreateCreatedBody) error {
+func (client dockerClient) doStartContainer(bg context.Context, c Container, creation container.ContainerCreateCreatedBody) 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 {
@@ -271,6 +284,67 @@ func (client dockerClient) RemoveImage(c Container) error {
return err
}
+func (client dockerClient) ExecuteCommand(containerID string, command string) error {
+ bg := context.Background()
+
+ // Create the exec
+ execConfig := types.ExecConfig{
+ Tty: true,
+ Detach: false,
+ Cmd: []string{"sh", "-c", command},
+ }
+
+ exec, err := client.api.ContainerExecCreate(bg, containerID, execConfig)
+ if err != nil {
+ return err
+ }
+
+ response, attachErr := client.api.ContainerExecAttach(bg, exec.ID, types.ExecStartCheck{
+ Tty: true,
+ Detach: false,
+ })
+ if attachErr != nil {
+ log.Errorf("Failed to extract command exec logs: %v", attachErr)
+ }
+
+ // Run the exec
+ execStartCheck := types.ExecStartCheck{Detach: false, Tty: true}
+ err = client.api.ContainerExecStart(bg, exec.ID, execStartCheck)
+ if err != nil {
+ return err
+ }
+
+ var execOutput string
+ if attachErr == nil {
+ defer response.Close()
+ var writer bytes.Buffer
+ written, err := writer.ReadFrom(response.Reader)
+ if err != nil {
+ log.Error(err)
+ } else if written > 0 {
+ execOutput = strings.TrimSpace(writer.String())
+ }
+ }
+
+ // Inspect the exec to get the exit code and print a message if the
+ // exit code is not success.
+ execInspect, err := client.api.ContainerExecInspect(bg, exec.ID)
+ if err != nil {
+ return err
+ }
+
+ if execInspect.ExitCode > 0 {
+ log.Errorf("Command exited with code %v.", execInspect.ExitCode)
+ log.Error(execOutput)
+ } else {
+ if len(execOutput) > 0 {
+ log.Infof("Command output:\n%v", execOutput)
+ }
+ }
+
+ return nil
+}
+
func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Duration) error {
bg := context.Background()
timeout := time.After(waitTime)
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 14b0e86..09e4225 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -10,13 +10,6 @@ import (
dockercontainer "github.com/docker/docker/api/types/container"
)
-const (
- watchtowerLabel = "com.centurylinklabs.watchtower"
- signalLabel = "com.centurylinklabs.watchtower.stop-signal"
- enableLabel = "com.centurylinklabs.watchtower.enable"
- zodiacLabel = "com.centurylinklabs.zodiac.original-image"
-)
-
// NewContainer returns a new Container instance instantiated with the
// specified ContainerInfo and ImageInfo structs.
func NewContainer(containerInfo *types.ContainerJSON, imageInfo *types.ImageInspect) *Container {
@@ -28,7 +21,8 @@ func NewContainer(containerInfo *types.ContainerJSON, imageInfo *types.ImageInsp
// Container represents a running Docker container.
type Container struct {
- Stale bool
+ Linked bool
+ Stale bool
containerInfo *types.ContainerJSON
imageInfo *types.ImageInspect
@@ -62,7 +56,7 @@ func (c Container) ImageID() string {
// "latest" tag is assumed.
func (c Container) ImageName() string {
// Compatibility w/ Zodiac deployments
- imageName, ok := c.containerInfo.Config.Labels[zodiacLabel]
+ imageName, ok := c.getLabelValue(zodiacLabel)
if !ok {
imageName = c.containerInfo.Config.Image
}
@@ -77,7 +71,7 @@ func (c Container) ImageName() string {
// Enabled returns the value of the container enabled label and if the label
// was set.
func (c Container) Enabled() (bool, bool) {
- rawBool, ok := c.containerInfo.Config.Labels[enableLabel]
+ rawBool, ok := c.getLabelValue(enableLabel)
if !ok {
return false, false
}
@@ -105,6 +99,12 @@ func (c Container) Links() []string {
return links
}
+// ToRestart return whether the container should be restarted, either because
+// is stale or linked to another stale container.
+func (c Container) ToRestart() bool {
+ return c.Stale || c.Linked
+}
+
// IsWatchtower returns a boolean flag indicating whether or not the current
// container is the watchtower container itself. The watchtower container is
// identified by the presence of the "com.centurylinklabs.watchtower" label in
@@ -117,11 +117,7 @@ func (c Container) IsWatchtower() bool {
// container's metadata. If the container has not specified a custom stop
// signal, the empty string "" is returned.
func (c Container) StopSignal() string {
- if val, ok := c.containerInfo.Config.Labels[signalLabel]; ok {
- return val
- }
-
- return ""
+ return c.getLabelValueOrEmpty(signalLabel)
}
// Ideally, we'd just be able to take the ContainerConfig from the old container
@@ -189,10 +185,3 @@ func (c Container) hostConfig() *dockercontainer.HostConfig {
return hostConfig
}
-
-// ContainsWatchtowerLabel takes a map of labels and values and tells
-// the consumer whether it contains a valid watchtower instance label
-func ContainsWatchtowerLabel(labels map[string]string) bool {
- val, ok := labels[watchtowerLabel]
- return ok && val == "true"
-}
diff --git a/pkg/container/metadata.go b/pkg/container/metadata.go
new file mode 100644
index 0000000..3ab9ec2
--- /dev/null
+++ b/pkg/container/metadata.go
@@ -0,0 +1,39 @@
+package container
+
+const (
+ watchtowerLabel = "com.centurylinklabs.watchtower"
+ signalLabel = "com.centurylinklabs.watchtower.stop-signal"
+ enableLabel = "com.centurylinklabs.watchtower.enable"
+ zodiacLabel = "com.centurylinklabs.zodiac.original-image"
+ preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
+ postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
+)
+
+// GetLifecyclePreUpdateCommand returns the pre-update command set in the container metadata or an empty string
+func (c Container) GetLifecyclePreUpdateCommand() string {
+ return c.getLabelValueOrEmpty(preUpdateLabel)
+}
+
+// GetLifecyclePostUpdateCommand returns the post-update command set in the container metadata or an empty string
+func (c Container) GetLifecyclePostUpdateCommand() string {
+ return c.getLabelValueOrEmpty(postUpdateLabel)
+}
+
+// ContainsWatchtowerLabel takes a map of labels and values and tells
+// the consumer whether it contains a valid watchtower instance label
+func ContainsWatchtowerLabel(labels map[string]string) bool {
+ val, ok := labels[watchtowerLabel]
+ return ok && val == "true"
+}
+
+func (c Container) getLabelValueOrEmpty(label string) string {
+ if val, ok := c.containerInfo.Config.Labels[label]; ok {
+ return val
+ }
+ return ""
+}
+
+func (c Container) getLabelValue(label string) (string, bool) {
+ val, ok := c.containerInfo.Config.Labels[label]
+ return val, ok
+}
diff --git a/scripts/lifecycle-tests.sh b/scripts/lifecycle-tests.sh
new file mode 100755
index 0000000..dd41823
--- /dev/null
+++ b/scripts/lifecycle-tests.sh
@@ -0,0 +1,208 @@
+#!/usr/bin/env bash
+
+set -e
+
+IMAGE=server
+CONTAINER=server
+LINKED_IMAGE=linked
+LINKED_CONTAINER=linked
+WATCHTOWER_INTERVAL=2
+
+function remove_container {
+ docker kill $1 >> /dev/null || true && docker rm -v $1 >> /dev/null || true
+}
+
+function cleanup {
+ # Do cleanup on exit or error
+ echo "Final cleanup"
+ sleep 2
+ remove_container $CONTAINER
+ remove_container $LINKED_CONTAINER
+ pkill -9 -f watchtower >> /dev/null || true
+}
+trap cleanup EXIT
+
+DEFAULT_WATCHTOWER="$(dirname "${BASH_SOURCE[0]}")/../watchtower"
+WATCHTOWER=$1
+WATCHTOWER=${WATCHTOWER:-$DEFAULT_WATCHTOWER}
+echo "watchtower path is $WATCHTOWER"
+
+##################################################################################
+##### PREPARATION ################################################################
+##################################################################################
+
+# Create Dockerfile template
+DOCKERFILE=$(cat << EOF
+FROM node:alpine
+
+LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="cat /opt/test/value.txt"
+LABEL com.centurylinklabs.watchtower.lifecycle.post-update="echo image > /opt/test/value.txt"
+
+ENV IMAGE_TIMESTAMP=TIMESTAMP
+
+WORKDIR /opt/test
+ENTRYPOINT ["/usr/local/bin/node", "/opt/test/server.js"]
+
+EXPOSE 8888
+
+RUN mkdir -p /opt/test && echo "default" > /opt/test/value.txt
+COPY server.js /opt/test/server.js
+EOF
+)
+
+# Create temporary directory to build docker image
+TMP_DIR="/tmp/watchtower-commands-test"
+mkdir -p $TMP_DIR
+
+# Create simple http server
+cat > $TMP_DIR/server.js << EOF
+const http = require("http");
+const fs = require("fs");
+
+http.createServer(function(request, response) {
+ const fileContent = fs.readFileSync("/opt/test/value.txt");
+ response.writeHead(200, {"Content-Type": "text/plain"});
+ response.write(fileContent);
+ response.end();
+}).listen(8888, () => { console.log('server is listening on 8888'); });
+EOF
+
+function builddocker {
+ TIMESTAMP=$(date +%s)
+ echo "Building image $TIMESTAMP"
+ echo "${DOCKERFILE/TIMESTAMP/$TIMESTAMP}" > $TMP_DIR/Dockerfile
+ docker build $TMP_DIR -t $IMAGE >> /dev/null
+}
+
+# Start watchtower
+echo "Starting watchtower"
+$WATCHTOWER -i $WATCHTOWER_INTERVAL --no-pull --stop-timeout 2s --enable-lifecycle-hooks $CONTAINER $LINKED_CONTAINER &
+sleep 3
+
+echo "#################################################################"
+echo "##### TEST CASE 1: Execute commands from base image"
+echo "#################################################################"
+
+# Build base image
+builddocker
+
+# Run container
+docker run -d -p 0.0.0.0:8888:8888 --name $CONTAINER $IMAGE:latest >> /dev/null
+sleep 1
+echo "Container $CONTAINER is runnning"
+
+# Test default value
+RESP=$(curl -s http://localhost:8888)
+if [ $RESP != "default" ]; then
+ echo "Default value of container response is invalid" 1>&2
+ exit 1
+fi
+
+# Build updated image to trigger watchtower update
+builddocker
+
+WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
+echo "Wait for $WAIT_AMOUNT seconds"
+sleep $WAIT_AMOUNT
+
+# Test value after post-update-command
+RESP=$(curl -s http://localhost:8888)
+if [[ $RESP != "image" ]]; then
+ echo "Value of container response is invalid. Expected: image. Actual: $RESP"
+ exit 1
+fi
+
+remove_container $CONTAINER
+
+echo "#################################################################"
+echo "##### TEST CASE 2: Execute commands from container and base image"
+echo "#################################################################"
+
+# Build base image
+builddocker
+
+# Run container
+docker run -d -p 0.0.0.0:8888:8888 \
+ --label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
+ --name $CONTAINER $IMAGE:latest >> /dev/null
+sleep 1
+echo "Container $CONTAINER is runnning"
+
+# Test default value
+RESP=$(curl -s http://localhost:8888)
+if [ $RESP != "default" ]; then
+ echo "Default value of container response is invalid" 1>&2
+ exit 1
+fi
+
+# Build updated image to trigger watchtower update
+builddocker
+
+WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
+echo "Wait for $WAIT_AMOUNT seconds"
+sleep $WAIT_AMOUNT
+
+# Test value after post-update-command
+RESP=$(curl -s http://localhost:8888)
+if [[ $RESP != "container" ]]; then
+ echo "Value of container response is invalid. Expected: container. Actual: $RESP"
+ exit 1
+fi
+
+remove_container $CONTAINER
+
+echo "#################################################################"
+echo "##### TEST CASE 3: Execute commands with a linked container"
+echo "#################################################################"
+
+# Tag the current image to keep a version for the linked container
+docker tag $IMAGE:latest $LINKED_IMAGE:latest
+
+# Build base image
+builddocker
+
+# Run container
+docker run -d -p 0.0.0.0:8888:8888 \
+ --label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
+ --name $CONTAINER $IMAGE:latest >> /dev/null
+docker run -d -p 0.0.0.0:8989:8888 \
+ --label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
+ --link $CONTAINER \
+ --name $LINKED_CONTAINER $LINKED_IMAGE:latest >> /dev/null
+sleep 1
+echo "Container $CONTAINER and $LINKED_CONTAINER are runnning"
+
+# Test default value
+RESP=$(curl -s http://localhost:8888)
+if [ $RESP != "default" ]; then
+ echo "Default value of container response is invalid" 1>&2
+ exit 1
+fi
+
+# Test default value for linked container
+RESP=$(curl -s http://localhost:8989)
+if [ $RESP != "default" ]; then
+ echo "Default value of linked container response is invalid" 1>&2
+ exit 1
+fi
+
+# Build updated image to trigger watchtower update
+builddocker
+
+WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
+echo "Wait for $WAIT_AMOUNT seconds"
+sleep $WAIT_AMOUNT
+
+# Test value after post-update-command
+RESP=$(curl -s http://localhost:8888)
+if [[ $RESP != "container" ]]; then
+ echo "Value of container response is invalid. Expected: container. Actual: $RESP"
+ exit 1
+fi
+
+# Test that linked container did not execute pre/post-update-command
+RESP=$(curl -s http://localhost:8989)
+if [[ $RESP != "default" ]]; then
+ echo "Value of linked container response is invalid. Expected: default. Actual: $RESP"
+ exit 1
+fi
\ No newline at end of file
From 3cab3126a72feb6481aff7b8795f3ebd96553205 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 27 Jul 2019 11:12:54 +0200
Subject: [PATCH 161/761] docs: add alexandremenif as a contributor (#353)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index d41fdf2..f032a92 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -311,6 +311,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "alexandremenif",
+ "name": "Alexandre Menif",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/16152103?v=4",
+ "profile": "https://alexandre.menif.name",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index ed8fb56..9a837c9 100644
--- a/README.md
+++ b/README.md
@@ -101,8 +101,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Mark Woodbridge đģ |
 Simon Aronsson đģ đ§ đ |
 Ansem93 đ |
-  Zois Pagoulatos đģ |
 Luka Peschke đģ đ |
+  Zois Pagoulatos đģ |
+  Alexandre Menif đģ |
From ab921407ab0390f472911982cb7631398f54a6ac Mon Sep 17 00:00:00 2001
From: Andrey
Date: Mon, 29 Jul 2019 23:42:40 +0400
Subject: [PATCH 162/761] Fix typo in arguments docs (#355)
---
docs/arguments.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 3631476..2472cd2 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -91,7 +91,7 @@ Environment Variable: WATCHTOWER_INCLUDE_STOPPED
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
```
- Argument: ---interval, -i
+ Argument: --interval, -i
Environment Variable: WATCHTOWER_POLL_INTERVAL
Type: Integer
Default: 300
From dea3b6d0db68470e335213b4d0a6bbc3279b5264 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Mon, 29 Jul 2019 21:42:47 +0200
Subject: [PATCH 163/761] docs: add chugunov as a contributor (#356)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 1 +
2 files changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f032a92..1bac359 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -320,6 +320,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "chugunov",
+ "name": "Andrey",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/4140479?v=4",
+ "profile": "https://github.com/chugunov",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 9a837c9..8b7d472 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Luka Peschke đģ đ |
 Zois Pagoulatos đģ |
 Alexandre Menif đģ |
+  Andrey đ |
From b05cb17c99e85e801edf4efcab2482554a8a6b8d Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 2 Aug 2019 14:13:47 +0200
Subject: [PATCH 164/761] fix exempt labels
---
.github/stale.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/stale.yml b/.github/stale.yml
index f59106c..1b245b9 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -2,8 +2,10 @@ daysUntilStale: 60
daysUntilClose: 7
exemptMilestones: true
exemptLabels:
- - pinned
- - security
+ - "Public Service Announcement"
+ - "Do not close"
+ - "Type: Bug"
+ - "Type: Security"
staleLabel: "Status: Stale"
markComment: >
This issue has been automatically marked as stale because it has not had
From 56fbede32d3d2f3ff78f3b15f5ba890de81f5f8c Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 2 Aug 2019 20:58:14 +0200
Subject: [PATCH 165/761] add docker pull count badge
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 8b7d472..7c338c4 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,9 @@
+
+
+
## Quick Start
From 7a30fd71025b005cd7e24d3c097cdcf8547ed3ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Armando=20L=C3=BCscher?=
Date: Fri, 9 Aug 2019 16:38:46 +0200
Subject: [PATCH 166/761] Correcting a few typos and text styling. (#359)
---
docs/arguments.md | 10 +++++-----
docs/container-selection.md | 2 +-
docs/lifecycle-hooks.md | 4 ++--
docs/linked-containers.md | 2 +-
docs/notifications.md | 4 ++--
docs/usage-overview.md | 4 ++--
6 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 2472cd2..90165d8 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -12,8 +12,8 @@ $ docker run -d \
```
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other
-running containers will be ignored. If you do not want watchtower to run as a daemon you can pass a run-once flag and remove
-the watchtower container after it's execution.
+running containers will be ignored. If you do not want watchtower to run as a daemon you can pass the `--run-once` flag and remove
+the watchtower container after its execution.
```bash
$ docker run --rm \
@@ -23,7 +23,7 @@ $ docker run --rm \
nginx redis
```
-In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the "--rm" flag.
+In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed, as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the `--rm` flag.
When no arguments are specified, watchtower will monitor all running containers.
@@ -48,7 +48,7 @@ Environment Variable: WATCHTOWER_CLEANUP
```
## Remove attached volumes
-Removes attached volumes after updating. When this flag is specified, watchtower will remove all attached volumes from the container before restarting container with a new image. Use this option to force new volumes to be populated as containers are updated.
+Removes attached volumes after updating. When this flag is specified, watchtower will remove all attached volumes from the container before restarting with a new image. Use this option to force new volumes to be populated as containers are updated.
```
Argument: --remove-volumes
@@ -87,7 +87,7 @@ Environment Variable: WATCHTOWER_INCLUDE_STOPPED
Default: false
```
-## Poll Interval
+## Poll interval
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
```
diff --git a/docs/container-selection.md b/docs/container-selection.md
index 0e2cd20..4c3312c 100644
--- a/docs/container-selection.md
+++ b/docs/container-selection.md
@@ -12,7 +12,7 @@ Or, it can be specified as part of the `docker run` command line:
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
```
-If you need to only include only some containers, pass the --label-enable flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of true for the containers you want to watch.
+If you need to include only some containers, pass the `--label-enable` flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
diff --git a/docs/lifecycle-hooks.md b/docs/lifecycle-hooks.md
index bbf9d21..97d254e 100644
--- a/docs/lifecycle-hooks.md
+++ b/docs/lifecycle-hooks.md
@@ -11,14 +11,14 @@ executed after restarting the container.
This feature is disabled by default. To enable it, you need to set the option
`--enable-lifecycle-hooks` on the command line, or set the environment variable
-`WATCHTOWER_LIFECYCLE_HOOKS` to true.
+`WATCHTOWER_LIFECYCLE_HOOKS` to `true`.
### Specifying update commands
The commands are specified using docker container labels, with
-`com.centurylinklabs.watchtower.pre-update-command` for the *pre-update*
+`com.centurylinklabs.watchtower.lifecycle.pre-update-command` for the *pre-update*
command and `com.centurylinklabs.watchtower.lifecycle.post-update` for the
*post-update* command.
diff --git a/docs/linked-containers.md b/docs/linked-containers.md
index 133d3ca..6960b5b 100644
--- a/docs/linked-containers.md
+++ b/docs/linked-containers.md
@@ -1,3 +1,3 @@
-Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
+Watchtower will detect if there are links between any of the running containers and ensures that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
\ No newline at end of file
diff --git a/docs/notifications.md b/docs/notifications.md
index 853ee63..af6a5ec 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -47,7 +47,7 @@ If watchtower is monitoring the same Docker daemon under which the watchtower co
To receive notifications in Slack, add `slack` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
-Additionally, you should set the Slack webhook url using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable.
+Additionally, you should set the Slack webhook URL using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable.
By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
@@ -76,7 +76,7 @@ docker run -d \
To receive notifications in MSTeams channel, add `msteams` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
-Additionally, you should set the MSTeams webhook url using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
+Additionally, you should set the MSTeams webhook URL using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable.
MSTeams notifier could send keys/values filled by `log.WithField` or `log.WithFields` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
diff --git a/docs/usage-overview.md b/docs/usage-overview.md
index fcc2039..f74a9a7 100644
--- a/docs/usage-overview.md
+++ b/docs/usage-overview.md
@@ -1,6 +1,6 @@
Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
-Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the -v flag when you run it.
+Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the `-v` flag when you run it.
Run the `watchtower` container with the following command:
@@ -37,7 +37,7 @@ docker run -d \
containrrr/watchtower container_to_watch --debug
```
-If you mount the config file as described above, be sure to also prepend the url for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at dockerhub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
+If you mount the config file as described above, be sure to also prepend the URL for the registry when starting up your watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container from a private repo at Docker Hub and monitors it with watchtower. Note the command argument changing the interval to 30s rather than the default 5 minutes.
```json
version: "3"
From 7b0167095927dd9857f394a840b07cd527964b75 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Fri, 9 Aug 2019 16:39:02 +0200
Subject: [PATCH 167/761] docs: add noplanman as a contributor (#364)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 3 +++
2 files changed, 12 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 1bac359..dbda74d 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -329,6 +329,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "noplanman",
+ "name": "Armando LÃŧscher",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/9423417?v=4",
+ "profile": "https://noplanman.ch",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 7c338c4..cde1c52 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Alexandre Menif đģ |
 Andrey đ |
+
+  Armando LÃŧscher đ |
+
From 573a3b3f1dcd75ee381b84674725e5a43dfa0cd3 Mon Sep 17 00:00:00 2001
From: Ryan Budke
Date: Fri, 9 Aug 2019 10:39:51 -0400
Subject: [PATCH 168/761] Wording clarification on "Filter by enable label"
(#357)
This is a super minor change, but the wording kept tripping me up, so I rephrased it.
---
docs/arguments.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 90165d8..77f1d0c 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -98,7 +98,7 @@ Environment Variable: WATCHTOWER_POLL_INTERVAL
```
## Filter by enable label
-Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true.
+Update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.
```
Argument: --label-enable
From 5a6b63a5c7b6e974ad17f3d3d4959bb0cb96c5dc Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Fri, 9 Aug 2019 16:40:02 +0200
Subject: [PATCH 169/761] docs: add rjbudke as a contributor (#365)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 1 +
2 files changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index dbda74d..a9715d0 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -338,6 +338,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "rjbudke",
+ "name": "Ryan Budke",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/273485?v=4",
+ "profile": "https://github.com/rjbudke",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index cde1c52..97ced3a 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Armando LÃŧscher đ |
+  Ryan Budke đ |
From f20bdb15f72ed58dcaf1b034bfd6e2d3903b51a4 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 23 Aug 2019 14:07:09 +0200
Subject: [PATCH 170/761] add information on how to use credential helpers
---
docs/credential-helpers.md | 64 ++++++++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 docs/credential-helpers.md
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
new file mode 100644
index 0000000..1722906
--- /dev/null
+++ b/docs/credential-helpers.md
@@ -0,0 +1,64 @@
+Some private docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
+To be able to use this together with watchtower, we need to use a credential helper.
+
+To keep the image size small we've decided to not include any helpers in the watchtower image, instead we'll put the
+helper in a separate container and mount it using volumes.
+
+### Example
+Example implementation for use with [amazon-ecr-credential-helper](https://github.com/awslabs/amazon-ecr-credential-helper):
+
+```Dockerfile
+FROM golang:latest
+
+ENV CGO_ENABLED 0
+ENV REPO github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login
+
+RUN go get -u $REPO
+
+RUN rm /go/bin/docker-credential-ecr-login
+
+RUN go build \
+ -o /go/bin/docker-credential-ecr-login \
+ /go/src/$REPO
+
+WORKDIR /go/bin/
+```
+
+and the docker-compose definition:
+```yaml
+version: "3"
+
+services:
+ watchtower:
+ image: index.docker.io/containrrr/watchtower:latest
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /.docker/config.json:/config.json
+ - helper:/go/bin
+ environment:
+ - HOME=/
+ - PATH=$PATH:/go/bin
+ - AWS_REGION=
+ - AWS_ACCESS_KEY_ID=
+ - AWS_SECRET_ACCESS_KEY=
+volumes:
+ helper: {}
+```
+
+and for `.docker/config.yml`:
+```yaml
+ {
+ "HttpHeaders" : {
+ "User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
+ },
+ "credsStore" : "osxkeychain", // ...or your prefered helper
+ "auths" : {
+ "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : {},
+ "https://index.docker.io/v1/": {}
+ },
+ "credHelpers": {
+ "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : "ecr-login",
+ "index.docker.io": "osxkeychain" // ...or your prefered helper
+ }
+ }
+```
\ No newline at end of file
From 7f7db72686f57653813042c4160104add15596e7 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 23 Aug 2019 14:08:38 +0200
Subject: [PATCH 171/761] add new documentation to menu
---
mkdocs.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/mkdocs.yml b/mkdocs.yml
index d64041b..9656c6d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,6 +14,7 @@ nav:
- 'Arguments': 'arguments.md'
- 'Notifications': 'notifications.md'
- 'Container selection': 'container-selection.md'
+ - 'Credential helpers': 'credential-helpers.md'
- 'Linked containers': 'linked-containers.md'
- 'Remote hosts': 'remote-hosts.md'
- 'Secure connections': 'secure-connections.md'
From f820eb5b3a7863ce46706503712dfb2686f73f64 Mon Sep 17 00:00:00 2001
From: Kaloyan Raev
Date: Sun, 25 Aug 2019 13:37:20 +0300
Subject: [PATCH 172/761] Add docker api version parameter (#372)
* Add docker api version parameter
* Note for minimum supported version
* Tests for EnvConfig
---
cmd/root.go | 6 +-----
docs/arguments.md | 12 ++++++++++-
internal/flags/flags.go | 14 +++++++++++--
internal/flags/flags_test.go | 39 ++++++++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+), 8 deletions(-)
create mode 100644 internal/flags/flags_test.go
diff --git a/cmd/root.go b/cmd/root.go
index b18ba06..8052e3b 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -18,10 +18,6 @@ import (
"github.com/spf13/cobra"
)
-// DockerAPIMinVersion is the minimum version of the docker api required to
-// use watchtower
-const DockerAPIMinVersion string = "1.24"
-
var (
client container.Client
scheduleSpec string
@@ -90,7 +86,7 @@ func PreRun(cmd *cobra.Command, args []string) {
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
// configure environment vars for client
- err := flags.EnvConfig(cmd, DockerAPIMinVersion)
+ err := flags.EnvConfig(cmd)
if err != nil {
log.Fatal(err)
}
diff --git a/docs/arguments.md b/docs/arguments.md
index 77f1d0c..db51c95 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -75,7 +75,17 @@ Docker daemon socket to connect to. Can be pointed at a remote Docker host by sp
Environment Variable: DOCKER_HOST
Type: String
Default: "unix:///var/run/docker.sock"
-```
+```
+
+## Docker API version
+The API version to use by the Docker client for connecting to the Docker daemon. The minimum supported version is 1.24.
+
+```
+ Argument: --api-version, -a
+Environment Variable: DOCKER_API_VERSION
+ Type: String
+ Default: "1.24"
+```
## Include stopped
Will also include created and exited containers.
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index d416243..179bc63 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -9,11 +9,16 @@ import (
"github.com/spf13/viper"
)
+// DockerAPIMinVersion is the minimum version of the docker api required to
+// use watchtower
+const DockerAPIMinVersion string = "1.24"
+
// RegisterDockerFlags that are used directly by the docker api client
func RegisterDockerFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags()
flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
+ flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client")
}
// RegisterSystemFlags that are used by watchtower to modify the program flow
@@ -215,6 +220,7 @@ Should only be used for testing.
func SetDefaults() {
viper.AutomaticEnv()
viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
+ viper.SetDefault("DOCKER_API_VERSION", DockerAPIMinVersion)
viper.SetDefault("WATCHTOWER_POLL_INTERVAL", 300)
viper.SetDefault("WATCHTOWER_TIMEOUT", time.Second*10)
viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{})
@@ -225,10 +231,11 @@ func SetDefaults() {
// EnvConfig translates the command-line options into environment variables
// that will initialize the api client
-func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
+func EnvConfig(cmd *cobra.Command) error {
var err error
var host string
var tls bool
+ var version string
flags := cmd.PersistentFlags()
@@ -238,13 +245,16 @@ func EnvConfig(cmd *cobra.Command, dockerAPIMinVersion string) error {
if tls, err = flags.GetBool("tlsverify"); err != nil {
return err
}
+ if version, err = flags.GetString("api-version"); err != nil {
+ return err
+ }
if err = setEnvOptStr("DOCKER_HOST", host); err != nil {
return err
}
if err = setEnvOptBool("DOCKER_TLS_VERIFY", tls); err != nil {
return err
}
- if err = setEnvOptStr("DOCKER_API_VERSION", dockerAPIMinVersion); err != nil {
+ if err = setEnvOptStr("DOCKER_API_VERSION", version); err != nil {
return err
}
return nil
diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go
new file mode 100644
index 0000000..ac57b30
--- /dev/null
+++ b/internal/flags/flags_test.go
@@ -0,0 +1,39 @@
+package flags
+
+import (
+ "os"
+ "testing"
+
+ "github.com/spf13/cobra"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestEnvConfig_Defaults(t *testing.T) {
+ cmd := new(cobra.Command)
+ SetDefaults()
+ RegisterDockerFlags(cmd)
+
+ err := EnvConfig(cmd)
+ require.NoError(t, err)
+
+ assert.Equal(t, "unix:///var/run/docker.sock", os.Getenv("DOCKER_HOST"))
+ assert.Equal(t, "", os.Getenv("DOCKER_TLS_VERIFY"))
+ assert.Equal(t, DockerAPIMinVersion, os.Getenv("DOCKER_API_VERSION"))
+}
+
+func TestEnvConfig_Custom(t *testing.T) {
+ cmd := new(cobra.Command)
+ SetDefaults()
+ RegisterDockerFlags(cmd)
+
+ err := cmd.ParseFlags([]string{"--host", "some-custom-docker-host", "--tlsverify", "--api-version", "1.99"})
+ require.NoError(t, err)
+
+ err = EnvConfig(cmd)
+ require.NoError(t, err)
+
+ assert.Equal(t, "some-custom-docker-host", os.Getenv("DOCKER_HOST"))
+ assert.Equal(t, "1", os.Getenv("DOCKER_TLS_VERIFY"))
+ assert.Equal(t, "1.99", os.Getenv("DOCKER_API_VERSION"))
+}
From ad6fd0e0b30960a9c5cdd1c6cb6792810d744001 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 25 Aug 2019 12:37:32 +0200
Subject: [PATCH 173/761] docs: add kaloyan-raev as a contributor (#374)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 10 ++++++++++
README.md | 1 +
2 files changed, 11 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index a9715d0..409f75e 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -347,6 +347,16 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "kaloyan-raev",
+ "name": "Kaloyan Raev",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/468091?v=4",
+ "profile": "http://kaloyan.raev.name",
+ "contributions": [
+ "code",
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 97ced3a..5e8d7dc 100644
--- a/README.md
+++ b/README.md
@@ -112,6 +112,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Armando LÃŧscher đ |
 Ryan Budke đ |
+  Kaloyan Raev đģ â ī¸ |
From bea8b9228f9f5c793e3b6fda0aeb456a7bdbdb2a Mon Sep 17 00:00:00 2001
From: sixth
Date: Sun, 25 Aug 2019 06:43:03 -0400
Subject: [PATCH 174/761] Update check.go (#370)
---
internal/actions/check.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/actions/check.go b/internal/actions/check.go
index 704d2d8..8574300 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -79,6 +79,6 @@ func createErrorIfAnyHaveOccurred(c int, i int) error {
}
func awaitDockerClient() {
- log.Debug("Sleeping for a seconds to ensure the docker api client has been properly initialized.")
+ log.Debug("Sleeping for a second to ensure the docker api client has been properly initialized.")
time.Sleep(1 * time.Second)
}
From 6ce142b4f5a78fed9740e6e905610ff83117e648 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 25 Aug 2019 12:43:12 +0200
Subject: [PATCH 175/761] docs: add sixth as a contributor (#375)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 1 +
2 files changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 409f75e..a02444c 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -357,6 +357,15 @@
"code",
"test"
]
+ },
+ {
+ "login": "sixth",
+ "name": "sixth",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/11591445?v=4",
+ "profile": "https://github.com/sixth",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 5e8d7dc..b985496 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Armando LÃŧscher đ |
 Ryan Budke đ |
 Kaloyan Raev đģ â ī¸ |
+  sixth đ |
From 17cbf86d486ad085b36191c518385a7a3b1f2b9b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 25 Aug 2019 13:02:08 +0200
Subject: [PATCH 176/761] fix: switch exit code for run once to 0
this resolves #347
---
cmd/root.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/root.go b/cmd/root.go
index 8052e3b..ea00786 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -112,7 +112,7 @@ func Run(c *cobra.Command, names []string) {
if runOnce {
log.Info("Running a one time update.")
runUpdatesWithNotifications(filter)
- os.Exit(1)
+ os.Exit(0)
return
}
From ce6ba0801ff6149e06e44ef3d3037ad4c85f1a89 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 25 Aug 2019 13:14:02 +0200
Subject: [PATCH 177/761] feature: add optional email delay
resolves #256
---
docs/notifications.md | 2 ++
internal/flags/flags.go | 6 ++++++
pkg/notifications/email.go | 13 +++++++++++--
3 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index af6a5ec..5741566 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -26,6 +26,7 @@ To receive notifications by email, the following command-line options, or their
- `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
+- `--notification-email-delay` (env. `WATCHTOWER_NOTIFICATION_EMAIL_DELAY`): Delay before sending notifications expressed in seconds.
Example:
@@ -39,6 +40,7 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
+ -e WATCHTOWER_NOTIFICATION_EMAIL_DELAY=2 \
containrrr/watchtower
```
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 179bc63..6e9ea55 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -128,6 +128,12 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
"Address to send notification emails to")
+
+ flags.IntP(
+ "notification-email-delay",
+ "",
+ viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
+ "Delay before sending notifications, expressed in seconds")
flags.StringP(
"notification-email-server",
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index 60db9cb..b5ef979 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -29,6 +29,7 @@ type emailTypeNotifier struct {
tlsSkipVerify bool
entries []*log.Entry
logLevels []log.Level
+ delay time.Duration
}
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
@@ -41,6 +42,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
password, _ := flags.GetString("notification-email-server-password")
port, _ := flags.GetInt("notification-email-server-port")
tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
+ delay, _ := flags.GetInt("notification-email-delay")
n := &emailTypeNotifier{
From: from,
@@ -51,6 +53,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
Port: port,
tlsSkipVerify: tlsSkipVerify,
logLevels: acceptedLogLevels,
+ delay: time.Duration(delay) * time.Second,
}
log.AddHook(n)
@@ -117,9 +120,15 @@ func (e *emailTypeNotifier) StartNotification() {
}
func (e *emailTypeNotifier) SendNotification() {
- if e.entries != nil && len(e.entries) != 0 {
- e.sendEntries(e.entries)
+ if e.entries == nil || len(e.entries) <= 0 {
+ return
}
+
+ if e.delay > 0 {
+ time.Sleep(e.delay)
+ }
+
+ e.sendEntries(e.entries)
e.entries = nil
}
From 1b3db5ed5d16a4a3f9387fcc6e923c7b576b495b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 15 Sep 2019 16:52:39 +0200
Subject: [PATCH 178/761] remove dead code and goverage dependency
---
.circleci/config.yml | 20 +-------------------
1 file changed, 1 insertion(+), 19 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c6b93d1..41183b5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -36,18 +36,9 @@ workflows:
only: /.*/
tags:
only: /.*/
- # - integration_testing:
- # requires:
- # - checkout
- # filters:
- # branches:
- # only: /.*/
- # tags:
- # only: /.*/
- build:
requires:
- testing
- # - integration_testing
- linting
filters:
branches:
@@ -96,17 +87,8 @@ jobs:
at: .
- run: go build ./...
- run: go get github.com/schrej/godacov
- - run: go get -u github.com/haya14busa/goverage
- - run: goverage -v -coverprofile=coverage.out ./...
+ - run: go test ./... -coverprofile coverage.out
- run: godacov -t $CODACY_TOKEN -r ./coverage.out -c $CIRCLE_SHA1
- #integration_testing:
- # executor: go
- # steps:
- # - attach_workspace:
- # at: .
- # - run: go build .
- # - setup_remote_docker
- # - run: ./scripts/lifecycle-tests.sh
build:
executor: go
steps:
From 135467dcf6ffc4f44f792d02a70cad90312bb31a Mon Sep 17 00:00:00 2001
From: Niklas Wigertz Danielsson
Date: Sun, 15 Sep 2019 16:58:46 +0200
Subject: [PATCH 179/761] add pre/post update check lifecycle hooks (#373)
* add pre/post update check lifecycle hooks
* update docs for lifecycle hooks
* Fix phrasing
---
docs/lifecycle-hooks.md | 38 +++++++++++++++++-----------
internal/actions/update.go | 52 ++++++++++++++++++++++++++++++++++++++
pkg/container/metadata.go | 12 +++++++++
3 files changed, 87 insertions(+), 15 deletions(-)
diff --git a/docs/lifecycle-hooks.md b/docs/lifecycle-hooks.md
index 97d254e..071726c 100644
--- a/docs/lifecycle-hooks.md
+++ b/docs/lifecycle-hooks.md
@@ -1,45 +1,53 @@
-
## Executing commands before and after updating
-> **DO NOTE**: Both commands are shell commands executed with `sh`, and therefore require the
+> **DO NOTE**: These are shell commands executed with `sh`, and therefore require the
> container to provide the `sh` executable.
-It is possible to execute a *pre-update* command and a *post-update* command
-**inside** every container updated by watchtower. The *pre-update* command is
-executed before stopping the container, and the *post-update* command is
-executed after restarting the container.
+It is possible to execute _pre/post\-check_ and _pre/post\-update_ commands
+**inside** every container updated by watchtower.
+
+- The _pre-check_ command is executed for each container prior to every update cycle.
+- The _pre-update_ command is executed before stopping the container when an update is about to start.
+- The _post-update_ command is executed after restarting the updated container
+- The _post-check_ command is executed for each container post every update cycle.
This feature is disabled by default. To enable it, you need to set the option
`--enable-lifecycle-hooks` on the command line, or set the environment variable
`WATCHTOWER_LIFECYCLE_HOOKS` to `true`.
-
-
### Specifying update commands
-The commands are specified using docker container labels, with
-`com.centurylinklabs.watchtower.lifecycle.pre-update-command` for the *pre-update*
-command and `com.centurylinklabs.watchtower.lifecycle.post-update` for the
-*post-update* command.
+The commands are specified using docker container labels, the following are currently available:
-These labels can be declared as instructions in a Dockerfile:
+| Type | Docker Container Label |
+| ----------- | ------------------------------------------------------ |
+| Pre Check | `com.centurylinklabs.watchtower.lifecycle.pre-check` |
+| Pre Update | `com.centurylinklabs.watchtower.lifecycle.pre-update` |
+| Post Update | `com.centurylinklabs.watchtower.lifecycle.post-update` |
+| Post Check | `com.centurylinklabs.watchtower.lifecycle.post-check` |
+
+These labels can be declared as instructions in a Dockerfile (with some example .sh files):
```docker
+LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh"
+LABEL com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh"
```
Or be specified as part of the `docker run` command line:
```bash
docker run -d \
+ --label=com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" \
someimage
+ --label=com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh" \
```
### Execution failure
-The failure of a command to execute, identified by an exit code different than
+The failure of a command to execute, identified by an exit code different than
0, will not prevent watchtower from updating the container. Only an error
-log statement containing the exit code will be reported.
\ No newline at end of file
+log statement containing the exit code will be reported.
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 5763017..02013d6 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -13,6 +13,8 @@ import (
func Update(client container.Client, params UpdateParams) error {
log.Debug("Checking containers for updated images")
+ executePreCheck(client, params)
+
containers, err := client.ListContainers(params.Filter)
if err != nil {
return err
@@ -36,12 +38,14 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers)
if params.MonitorOnly {
+ executePostCheck(client, params)
return nil
}
stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params)
+ executePostCheck(client, params)
return nil
}
@@ -123,11 +127,58 @@ func checkDependencies(containers []container.Container) {
}
}
+func executePreCheck(client container.Client, params UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ executePreCheckCommand(client, container)
+ }
+}
+
+func executePostCheck(client container.Client, params UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ executePostCheckCommand(client, container)
+ }
+}
+
+func executePreCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePreCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing pre-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+func executePostCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePostCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No post-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing post-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
func executePreUpdateCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreUpdateCommand()
if len(command) == 0 {
log.Debug("No pre-update command supplied. Skipping")
+ return
}
log.Info("Executing pre-update command.")
@@ -146,6 +197,7 @@ func executePostUpdateCommand(client container.Client, newContainerID string) {
command := newContainer.GetLifecyclePostUpdateCommand()
if len(command) == 0 {
log.Debug("No post-update command supplied. Skipping")
+ return
}
log.Info("Executing post-update command.")
diff --git a/pkg/container/metadata.go b/pkg/container/metadata.go
index 3ab9ec2..0e04350 100644
--- a/pkg/container/metadata.go
+++ b/pkg/container/metadata.go
@@ -5,10 +5,22 @@ const (
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
enableLabel = "com.centurylinklabs.watchtower.enable"
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
+ preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
+ postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
)
+// GetLifecyclePreCheckCommand returns the pre-check command set in the container metadata or an empty string
+func (c Container) GetLifecyclePreCheckCommand() string {
+ return c.getLabelValueOrEmpty(preCheckLabel)
+}
+
+// GetLifecyclePostCheckCommand returns the post-check command set in the container metadata or an empty string
+func (c Container) GetLifecyclePostCheckCommand() string {
+ return c.getLabelValueOrEmpty(postCheckLabel)
+}
+
// GetLifecyclePreUpdateCommand returns the pre-update command set in the container metadata or an empty string
func (c Container) GetLifecyclePreUpdateCommand() string {
return c.getLabelValueOrEmpty(preUpdateLabel)
From 5246d1a20e9a9d418f196ae70653db2ea1f4f809 Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Wed, 9 Oct 2019 09:18:39 +0200
Subject: [PATCH 180/761] Update email.go
Added SubjectTag as variable
---
pkg/notifications/email.go | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index b5ef979..ef6b3d7 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -24,7 +24,7 @@ const (
// We work around that by holding on to log entries until the update cycle is done.
type emailTypeNotifier struct {
From, To string
- Server, User, Password string
+ Server, User, Password, SubjectTag string
Port int
tlsSkipVerify bool
entries []*log.Entry
@@ -43,6 +43,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
port, _ := flags.GetInt("notification-email-server-port")
tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
delay, _ := flags.GetInt("notification-email-delay")
+ subjecttag, _ := flags.GetInt("notification-email-subjecttag")
n := &emailTypeNotifier{
From: from,
@@ -54,6 +55,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
tlsSkipVerify: tlsSkipVerify,
logLevels: acceptedLogLevels,
delay: time.Duration(delay) * time.Second,
+ SubjectTag: subjecttag,
}
log.AddHook(n)
@@ -62,7 +64,11 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
}
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
- emailSubject := "Watchtower updates"
+ if SubjectTag == nil {
+ emailSubject := "Watchtower updates"
+ } else {
+ emailSubject := SubjectTag + " Watchtower updates"
+ }
if hostname, err := os.Hostname(); err == nil {
emailSubject += " on " + hostname
}
From 220dc5add45173558fd655a13bf90419ebd4dec7 Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Wed, 9 Oct 2019 09:20:07 +0200
Subject: [PATCH 181/761] Update email.go
---
pkg/notifications/email.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index ef6b3d7..fdc8fee 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -43,7 +43,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
port, _ := flags.GetInt("notification-email-server-port")
tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
delay, _ := flags.GetInt("notification-email-delay")
- subjecttag, _ := flags.GetInt("notification-email-subjecttag")
+ subjecttag, _ := flags.GetString("notification-email-subjecttag")
n := &emailTypeNotifier{
From: from,
From 505a5ec71577ec3a3bb742fafece703ea73e01dd Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Wed, 9 Oct 2019 09:25:24 +0200
Subject: [PATCH 182/761] Update email.go
---
pkg/notifications/email.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index fdc8fee..7fb1500 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -64,6 +64,9 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
}
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
+ // If variable SubjectTag is empty define emailSubject as "Watchtower updates".
+ // If variable SubjectTag is set define emailSubject as "$SubjectTag Watchtower updates"
+ // For example: SubjectTag=[Server Munich] -> "[Server Munich] Watchtower updates ..."
if SubjectTag == nil {
emailSubject := "Watchtower updates"
} else {
From 48bca4765c95a7f7ca59ee87913730fa79f0b7cb Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Wed, 9 Oct 2019 17:35:14 +0200
Subject: [PATCH 183/761] Update FUNDING.yml
---
.github/FUNDING.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 364d198..419e96c 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
custom: https://www.amazon.com/hz/wishlist/ls/F94JJV822VX6
+github: simskij
From 19545204471af8b56fb7cfa71e5de968212f18b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?=
Date: Sat, 12 Oct 2019 11:56:19 +0200
Subject: [PATCH 184/761] Fix a small typo (#384)
---
pkg/notifications/gotify.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/notifications/gotify.go b/pkg/notifications/gotify.go
index 47ea884..c9c6af8 100644
--- a/pkg/notifications/gotify.go
+++ b/pkg/notifications/gotify.go
@@ -31,7 +31,7 @@ func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifi
} else if !(strings.HasPrefix(gotifyURL, "http://") || strings.HasPrefix(gotifyURL, "https://")) {
log.Fatal("Gotify URL must start with \"http://\" or \"https://\"")
} else if strings.HasPrefix(gotifyURL, "http://") {
- log.Warn("Using an HTTP url fpr Gotify is insecure")
+ log.Warn("Using an HTTP url for Gotify is insecure")
}
gotifyToken, _ := flags.GetString("notification-gotify-token")
From 42a349c423e72fdd4e1f793b5cbb554a2a6a2c9e Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 12 Oct 2019 11:56:27 +0200
Subject: [PATCH 185/761] docs: add foosel as a contributor (#391)
* docs: update README.md
* docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
README.md | 1 +
2 files changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index a02444c..f0f4374 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -366,6 +366,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "foosel",
+ "name": "Gina HäuÃge",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/83657?v=4",
+ "profile": "https://foosel.net",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index b985496..3fbe043 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Ryan Budke đ |
 Kaloyan Raev đģ â ī¸ |
 sixth đ |
+  Gina HäuÃge đģ |
From a2a62fb46e0637d694bb7f9adfbd60bf953223e7 Mon Sep 17 00:00:00 2001
From: Lukas Elsner
Date: Sun, 13 Oct 2019 14:46:06 -0600
Subject: [PATCH 186/761] do not delete same image twice when cleaning up
---
go.mod | 86 ++++++++++-----------
go.sum | 151 +++++++++++++++++++++++++++++--------
internal/actions/update.go | 18 +++--
3 files changed, 171 insertions(+), 84 deletions(-)
diff --git a/go.mod b/go.mod
index 1690237..5b6e517 100644
--- a/go.mod
+++ b/go.mod
@@ -3,64 +3,58 @@ module github.com/containrrr/watchtower
go 1.12
require (
- github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
- github.com/Microsoft/go-winio v0.4.12
- github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
- github.com/beorn7/perks v1.0.0
- github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111
- github.com/containerd/containerd v1.2.6 // indirect
- github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
- github.com/davecgh/go-spew v1.1.1
+ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
+ github.com/Microsoft/go-winio v0.4.12 // indirect
+ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
+ github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
+ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
+ github.com/bitly/go-simplejson v0.5.0 // indirect
+ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
+ github.com/bugsnag/bugsnag-go v1.5.3 // indirect
+ github.com/bugsnag/panicwrap v1.2.0 // indirect
+ github.com/cenkalti/backoff v2.2.1+incompatible // indirect
+ github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
+ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
+ github.com/deckarep/golang-set v1.7.1
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
- github.com/docker/docker-credential-helpers v0.6.1
- github.com/docker/go v1.5.1-1
- github.com/docker/go-connections v0.4.0
- github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82
- github.com/docker/go-units v0.3.3
- github.com/docker/swarmkit v1.12.0 // indirect
- github.com/gogo/protobuf v1.2.1
- github.com/golang/mock v1.1.1
- github.com/golang/protobuf v1.3.1
- github.com/gorilla/mux v1.7.0
- github.com/hashicorp/go-memdb v1.0.0 // indirect
- github.com/hashicorp/go-version v1.1.0
- github.com/inconshreveable/mousetrap v1.0.0
- github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22
+ github.com/docker/docker-credential-helpers v0.6.1 // indirect
+ github.com/docker/go v1.5.1-1 // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect
+ github.com/docker/go-units v0.3.3 // indirect
+ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
+ github.com/gofrs/uuid v3.2.0+incompatible // indirect
+ github.com/google/certificate-transparency-go v1.0.21 // indirect
+ github.com/gorilla/mux v1.7.0 // indirect
+ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
+ github.com/hashicorp/go-version v1.1.0 // indirect
+ github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/jinzhu/gorm v1.9.11 // indirect
+ github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 // indirect
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
- github.com/konsorten/go-windows-terminal-sequences v1.0.2
- github.com/mattn/go-shellwords v1.0.5 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.1
- github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa
- github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
+ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
+ github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
+ github.com/lib/pq v1.2.0 // indirect
+ github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa // indirect
+ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
- github.com/opencontainers/go-digest v1.0.0-rc1
- github.com/opencontainers/image-spec v1.0.1
+ github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
+ github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1
- github.com/opencontainers/runtime-spec v1.0.1 // indirect
- github.com/opencontainers/selinux v1.2.1 // indirect
- github.com/pkg/errors v0.8.1
- github.com/pmezard/go-difflib v1.0.0
- github.com/prometheus/client_golang v0.9.3
- github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
- github.com/prometheus/common v0.4.0
- github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084
+ github.com/pkg/errors v0.8.1 // indirect
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/sirupsen/logrus v1.4.1
github.com/spf13/cobra v0.0.3
- github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.4.0
- github.com/stretchr/objx v0.1.1
github.com/stretchr/testify v1.3.0
- github.com/theupdateframework/notary v0.6.1
- github.com/vbatts/tar-split v0.11.1 // indirect
- golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
+ github.com/theupdateframework/notary v0.6.1 // indirect
+ github.com/zmap/zlint v1.0.2 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
- golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
- google.golang.org/appengine v1.4.0 // indirect
- google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
- google.golang.org/grpc v1.21.0
+ gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
+ gopkg.in/fatih/pool.v2 v2.0.0 // indirect
+ gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index 2107c46..160a1c2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,25 +1,44 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
+cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111 h1:gRfsoKtF1tba+hVsNgo7OKG7a35hBK30ouOTHPgqFf8=
-github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111/go.mod h1:H1ipqq0hhUWJgVeQ5dbUe/C8YptJrE/VGDQp9bI+qTo=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
+github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04=
+github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
+github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
+github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/containerd/containerd v1.2.6 h1:K38ZSAA9oKSrX3iFNY+4SddZ8hH1TCMCerc8NHfcKBQ=
-github.com/containerd/containerd v1.2.6/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 h1:A7RURps5t4yDU0zktlgrE3Bdmjfv35nVs+xJdoWgIgY=
+github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -30,6 +49,10 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
+github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29 h1:ciaXDHaWQda0nvevWqcjtXX/buQY3e0lga1vq8Batq0=
@@ -48,38 +71,56 @@ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zF
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/swarmkit v1.12.0 h1:vcbNXevt9xOod0miQxkp9WZ70IsOCe8geXkmFnXP2e0=
-github.com/docker/swarmkit v1.12.0/go.mod h1:n3Z4lIEl7g261ptkGDBcYi/3qBMDl9csaAhwi2MPejs=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
+github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
+github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIUjTQ=
-github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -89,79 +130,97 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
+github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa h1:gOXc1BXmFuxWYmTfoK51YJR7srco3CwbsVHgr+8Y4r0=
github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/selinux v1.2.1 h1:Svlc+L67YcjN4K2bqD8Wlw9jtMlmZ+1FEGn6zsm8am0=
-github.com/opencontainers/selinux v1.2.1/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
-github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 h1:0aNv3xC7DmQoy1/x1sMh18g+fihWW68LL13i8ao9kl4=
-github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@@ -188,34 +247,45 @@ github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
+github.com/weppos/publicsuffix-go v0.4.0 h1:YSnfg3V65LcCFKtIGKGoBhkyKolEd0hlipcXaOjdnQw=
+github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
+github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
+github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
+github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
+github.com/zmap/zlint v1.0.2 h1:07+WuC/prlXVlWa1CJx2lCpuCd8biIeBAVnwTN2CPaA=
+github.com/zmap/zlint v1.0.2/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692 h1:GRhHqDOgeDr6QDTtq9gn2O4iKvm5dsbfqD/TXb0KLX0=
-golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b h1:/zjbcJPEGAyu6Is/VBOALsgdi4z9+kz/Vtdm6S+beD0=
-golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -226,32 +296,47 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2 h1:8FyEBtGg6Px24p+H2AkuVWqhj4+R9fo+fZD17mg+lzk=
-google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
-google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
+gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
+gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
+gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
+gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -261,4 +346,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 02013d6..4afa0f1 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -3,6 +3,7 @@ package actions
import (
"github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
+ "github.com/deckarep/golang-set"
log "github.com/sirupsen/logrus"
)
@@ -73,11 +74,22 @@ func stopStaleContainer(container container.Container, client container.Client,
}
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+ toDelete := mapset.NewSet()
for _, container := range containers {
if !container.Stale {
continue
}
restartStaleContainer(container, client, params)
+ toDelete.Add(container)
+ }
+ if params.Cleanup {
+ iterator := toDelete.Iterator()
+ for c := range iterator.C {
+ cont := c.(container.Container)
+ if err := client.RemoveImage(cont); err != nil {
+ log.Error(err)
+ }
+ }
}
}
@@ -100,12 +112,6 @@ func restartStaleContainer(container container.Container, client container.Clien
executePostUpdateCommand(client, newContainerID)
}
}
-
- if params.Cleanup {
- if err := client.RemoveImage(container); err != nil {
- log.Error(err)
- }
- }
}
func checkDependencies(containers []container.Container) {
From fd2a006c6bf8024e3cc69825da38cdb0fb7ca0d9 Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Tue, 15 Oct 2019 16:41:40 +0200
Subject: [PATCH 187/761] Update email.go
---
pkg/notifications/email.go | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index 7fb1500..4916d40 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -64,10 +64,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
}
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
- // If variable SubjectTag is empty define emailSubject as "Watchtower updates".
- // If variable SubjectTag is set define emailSubject as "$SubjectTag Watchtower updates"
- // For example: SubjectTag=[Server Munich] -> "[Server Munich] Watchtower updates ..."
- if SubjectTag == nil {
+ if SubjectTag == "" {
emailSubject := "Watchtower updates"
} else {
emailSubject := SubjectTag + " Watchtower updates"
From 6840a8c3cf2678f7937d75530b0661b7932c345a Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Tue, 15 Oct 2019 16:48:58 +0200
Subject: [PATCH 188/761] Update flags.go
---
internal/flags/flags.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 6e9ea55..971afa6 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -146,7 +146,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"",
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
"SMTP server port to send notification emails through")
-
+
flags.BoolP(
"notification-email-server-tls-skip-verify",
"",
@@ -168,6 +168,12 @@ Should only be used for testing.
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
"SMTP server password for sending notifications")
+ flags.StringP(
+ "notification-email-subjecttag",
+ "",
+ viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
+ "Subject prefix tag for notifications via mail")
+
flags.StringP(
"notification-slack-hook-url",
"",
@@ -232,6 +238,7 @@ func SetDefaults() {
viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{})
viper.SetDefault("WATCHTOWER_NOTIFICATIONS_LEVEL", "info")
viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT", 25)
+ viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG", "")
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
}
From 9198f8972befe6f78075f5d652ef0cb5d7ed1d5d Mon Sep 17 00:00:00 2001
From: Max H <10329648+8ear@users.noreply.github.com>
Date: Tue, 15 Oct 2019 16:51:13 +0200
Subject: [PATCH 189/761] Update flags.go
---
internal/flags/flags.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 971afa6..b2f9187 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -146,7 +146,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"",
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
"SMTP server port to send notification emails through")
-
+
flags.BoolP(
"notification-email-server-tls-skip-verify",
"",
From 81540bcb59eff56c43acc91ddaf1d4b261b489e2 Mon Sep 17 00:00:00 2001
From: foxbit19
Date: Tue, 29 Oct 2019 21:26:50 +0100
Subject: [PATCH 190/761] Insert nota bene about docker-compose into
notification doc page (#392)
---
docs/notifications.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/notifications.md b/docs/notifications.md
index 5741566..2f6d7ff 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -9,6 +9,11 @@ The types of notifications to send are passed via the comma-separated option `--
- `msteams` to send notifications via MSTeams webhook
- `gotify` to send notifications via Gotify
+
+> If you're a `docker-compose` user, make sure to specify environment variables' values in your `.yml` file without double quotes (`"`).
+>
+> This prevents unexpected errors when watchtower starts.
+
## Settings
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
From d53eed08cda6d968cc8f7320d77de23796516cf1 Mon Sep 17 00:00:00 2001
From: Jungkook Park
Date: Tue, 12 Nov 2019 14:59:07 +0900
Subject: [PATCH 191/761] docs: add missing arguments
* add a missing argument --no--restart
* add a missing env variable name for argument --debug
---
docs/arguments.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index db51c95..5072730 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -62,7 +62,7 @@ Enable debug mode with verbose logging.
```
Argument: --debug
-Environment Variable: N/A
+Environment Variable: WATCHTOWER_DEBUG
Type: Boolean
Default: false
```
@@ -127,6 +127,16 @@ Environment Variable: WATCHTOWER_MONITOR_ONLY
Default: false
```
+## Without restarting containers
+Do not restart containers after updating. This option can be useful when the start of the containers
+is managed by an external system such as systemd.
+```
+ Argument: --no-restart
+Environment Variable: WATCHTOWER_NO_RESTART
+ Type: Boolean
+ Default: false
+```
+
## Without pulling new images
Do not pull new images. When this flag is specified, watchtower will not attempt to pull
new images from the registry. Instead it will only monitor the local image cache for changes.
From 63e50491608e990fe9d8565145f9a8ae7a252b6b Mon Sep 17 00:00:00 2001
From: Zois Pagoulatos
Date: Wed, 13 Nov 2019 11:09:54 +0100
Subject: [PATCH 192/761] Add lifecycle hooks to documentation nav (#401)
Add lifecycle hooks to documentation nav
---
mkdocs.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mkdocs.yml b/mkdocs.yml
index 9656c6d..e5e7c34 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,5 +19,6 @@ nav:
- 'Remote hosts': 'remote-hosts.md'
- 'Secure connections': 'secure-connections.md'
- 'Stop signals': 'stop-signals.md'
+ - 'Lifecycle hooks': 'lifecycle-hooks.md'
plugins:
- - search
\ No newline at end of file
+ - search
From 2d8507ca313a3d726798cb07ac21e86ca4a79ab2 Mon Sep 17 00:00:00 2001
From: Zois Pagoulatos
Date: Wed, 13 Nov 2019 11:16:37 +0100
Subject: [PATCH 193/761] Add --revive-stopped flag to start stopped containers
after an update (#403)
* Add --revive-stopped flag to start stopped containers after an update
* Update arguments.md
---
cmd/root.go | 2 ++
docs/arguments.md | 10 ++++++++++
internal/flags/flags.go | 8 +++++++-
pkg/container/client.go | 6 ++++--
4 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/cmd/root.go b/cmd/root.go
index ea00786..453196a 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -93,11 +93,13 @@ func PreRun(cmd *cobra.Command, args []string) {
noPull, _ := f.GetBool("no-pull")
includeStopped, _ := f.GetBool("include-stopped")
+ reviveStopped, _ := f.GetBool("revive-stopped")
removeVolumes, _ := f.GetBool("remove-volumes")
client = container.NewClient(
!noPull,
includeStopped,
+ reviveStopped,
removeVolumes,
)
diff --git a/docs/arguments.md b/docs/arguments.md
index db51c95..dc2c0ee 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -97,6 +97,16 @@ Environment Variable: WATCHTOWER_INCLUDE_STOPPED
Default: false
```
+## Revive stopped
+Start any stopped containers that have had their image updated. This argument is only usable with the `--include-stopped` argument.
+
+```
+ Argument: --revive-stopped
+Environment Variable: WATCHTOWER_REVIVE_STOPPED
+ Type: Boolean
+ Default: false
+```
+
## Poll interval
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 6e9ea55..a8f330b 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -94,6 +94,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
"Will also include created and exited containers")
+ flags.BoolP(
+ "revive-stopped",
+ "",
+ viper.GetBool("WATCHTOWER_REVIVE_STOPPED"),
+ "Will also start stopped containers that were updated, if include-stopped is active")
+
flags.BoolP(
"enable-lifecycle-hooks",
"",
@@ -128,7 +134,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
"Address to send notification emails to")
-
+
flags.IntP(
"notification-email-delay",
"",
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 0dc22db..5877eb4 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -38,7 +38,7 @@ 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, includeStopped bool, removeVolumes bool) Client {
+func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool) Client {
cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
if err != nil {
@@ -50,6 +50,7 @@ func NewClient(pullImages bool, includeStopped bool, removeVolumes bool) Client
pullImages: pullImages,
removeVolumes: removeVolumes,
includeStopped: includeStopped,
+ reviveStopped: reviveStopped,
}
}
@@ -58,6 +59,7 @@ type dockerClient struct {
pullImages bool
removeVolumes bool
includeStopped bool
+ reviveStopped bool
}
func (client dockerClient) ListContainers(fn t.Filter) ([]Container, error) {
@@ -203,7 +205,7 @@ func (client dockerClient) StartContainer(c Container) (string, error) {
}
- if !c.IsRunning() {
+ if !c.IsRunning() && !client.reviveStopped {
return createdContainer.ID, nil
}
From 8b2eda7bdf126fa202eb2b57c8fbd1a4b5287c4c Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 17 Nov 2019 11:09:14 +0100
Subject: [PATCH 194/761] Update notifications.md
---
docs/notifications.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/notifications.md b/docs/notifications.md
index 5741566..22da93e 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -27,6 +27,7 @@ To receive notifications by email, the following command-line options, or their
- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
- `--notification-email-delay` (env. `WATCHTOWER_NOTIFICATION_EMAIL_DELAY`): Delay before sending notifications expressed in seconds.
+- `--notification-email-subjecttag` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG`): Prefix to include in the subject tag. Useful when running multiple watchtowers.
Example:
From 24902964016e7bdbd7ad569128415547db2a281b Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 17 Nov 2019 10:10:46 +0000
Subject: [PATCH 195/761] docs: update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 3fbe043..af5f345 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Kaloyan Raev đģ â ī¸ |
 sixth đ |
 Gina HäuÃge đģ |
+  Max H. đģ |
From 288f8c68ceabc02d02c0668b5f3ac586640d8abb Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 17 Nov 2019 10:10:47 +0000
Subject: [PATCH 196/761] docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f0f4374..ab949c6 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -375,6 +375,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "8ear",
+ "name": "Max H.",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/10329648?v=4",
+ "profile": "https://github.com/8ear",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From 91447853f48546d4191c238836a08c3bfe88bdf1 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 17 Nov 2019 10:12:05 +0000
Subject: [PATCH 197/761] docs: update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index af5f345..eb53ce9 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 sixth đ |
 Gina HäuÃge đģ |
 Max H. đģ |
+  Jungkook Park đ |
From c1a096a616660df2c9627d999e15c6cb9453059a Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 17 Nov 2019 10:12:06 +0000
Subject: [PATCH 198/761] docs: update .all-contributorsrc
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index ab949c6..dfaa6e3 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -384,6 +384,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "pjknkda",
+ "name": "Jungkook Park",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/4986524?v=4",
+ "profile": "https://pjknkda.github.io",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
From ea596ea74746d811b64effe45d9e260509854db5 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 17 Nov 2019 11:19:57 +0100
Subject: [PATCH 199/761] fix some var ref errors
---
go.mod | 1 -
internal/flags/flags.go | 2 +-
pkg/notifications/email.go | 22 ++++++++++++----------
3 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/go.mod b/go.mod
index 1690237..b5be153 100644
--- a/go.mod
+++ b/go.mod
@@ -59,7 +59,6 @@ require (
golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
- google.golang.org/appengine v1.4.0 // indirect
google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
google.golang.org/grpc v1.21.0
gotest.tools v2.2.0+incompatible // indirect
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 3f3d8fc..7ab09bd 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -179,7 +179,7 @@ Should only be used for testing.
"",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
"Subject prefix tag for notifications via mail")
-
+
flags.StringP(
"notification-slack-hook-url",
"",
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index 4916d40..ca54499 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -23,13 +23,13 @@ const (
// - It would only send errors
// We work around that by holding on to log entries until the update cycle is done.
type emailTypeNotifier struct {
- From, To string
+ From, To string
Server, User, Password, SubjectTag string
- Port int
- tlsSkipVerify bool
- entries []*log.Entry
- logLevels []log.Level
- delay time.Duration
+ Port int
+ tlsSkipVerify bool
+ entries []*log.Entry
+ logLevels []log.Level
+ delay time.Duration
}
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
@@ -64,10 +64,12 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
}
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
- if SubjectTag == "" {
- emailSubject := "Watchtower updates"
+ var emailSubject string
+
+ if e.SubjectTag == "" {
+ emailSubject = "Watchtower updates"
} else {
- emailSubject := SubjectTag + " Watchtower updates"
+ emailSubject = e.SubjectTag + " Watchtower updates"
}
if hostname, err := os.Hostname(); err == nil {
emailSubject += " on " + hostname
@@ -134,7 +136,7 @@ func (e *emailTypeNotifier) SendNotification() {
time.Sleep(e.delay)
}
- e.sendEntries(e.entries)
+ e.sendEntries(e.entries)
e.entries = nil
}
From 341d1b0ba1bfbc43eb50b0862916d701cac178f0 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 24 Nov 2019 14:44:57 +0100
Subject: [PATCH 200/761] Greet new reporters and contributors using actions
Replaces the welcome bot
---
.github/workflows/greetings.yml | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 .github/workflows/greetings.yml
diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml
new file mode 100644
index 0000000..a3a88df
--- /dev/null
+++ b/.github/workflows/greetings.yml
@@ -0,0 +1,25 @@
+name: Greetings
+
+on: [pull_request, issues]
+
+jobs:
+ greeting:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/first-interaction@v1
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-message: >
+ Hi there! đđŧ
+ As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md)
+ as well as our [contribution guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md).
+ \n
+ Thanks a bunch for opening your first issue! đ
+ pr-message: >
+ Congratulations on opening your first pull request! đĨŗ\n\n
+ To make sure that your pull request is handled as fast as possible, we recommend that you make sure that you make sure that you've done the following\:\n
+ [ ] Documentation has been updated
+ [ ] Tests have been added to all appropriate places
+ [ ] Changes adhere to our [contribution guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md)
+
+ Thanks! đđŧ
From 3464ddaf19835a3e75f0fe94d43d32767be12a94 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 24 Nov 2019 14:52:42 +0100
Subject: [PATCH 201/761] Update greetings.yml
---
.github/workflows/greetings.yml | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml
index a3a88df..7034c9e 100644
--- a/.github/workflows/greetings.yml
+++ b/.github/workflows/greetings.yml
@@ -13,13 +13,6 @@ jobs:
Hi there! đđŧ
As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md)
as well as our [contribution guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md).
- \n
Thanks a bunch for opening your first issue! đ
pr-message: >
- Congratulations on opening your first pull request! đĨŗ\n\n
- To make sure that your pull request is handled as fast as possible, we recommend that you make sure that you make sure that you've done the following\:\n
- [ ] Documentation has been updated
- [ ] Tests have been added to all appropriate places
- [ ] Changes adhere to our [contribution guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md)
-
- Thanks! đđŧ
+ Congratulations on opening your first pull request! We'll get back to you as soon as possible. In the meantime, please make sure you've updated the documentation to reflect your changes and have added test automation as needed. Thanks! đđŧ
From 8c9741022c6829610fe96a19caafbc5fcd137c05 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sun, 24 Nov 2019 14:53:42 +0100
Subject: [PATCH 202/761] remove welcome bot config
---
.github/config.yml | 11 -----------
1 file changed, 11 deletions(-)
delete mode 100644 .github/config.yml
diff --git a/.github/config.yml b/.github/config.yml
deleted file mode 100644
index 62993b5..0000000
--- a/.github/config.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-newIssueWelcomeComment: >
- Hi there!
-
- Thanks a bunch for opening your first issue! :pray:
- As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md)
-
-newPRWelcomeComment: >
- Thanks for opening this pull request! Please check out our [contributing guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md) as well as our [code of conduct](https://github.com/containrrr/watchtower/blob/master/CODE_OF_CONDUCT.md).
-
-firstPRMergeComment: >
- Congrats on merging your first pull request! We are all very proud of you! :sparkles:
From 68a6903685ba54052ee92da9dc016edef9debbff Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 26 Nov 2019 19:35:24 +0100
Subject: [PATCH 203/761] Update notifications.md
---
docs/notifications.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index 404c4b7..a731855 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -2,7 +2,7 @@
# Notifications
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
-The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
+The types of notifications to send are passed via the space-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
- `email` to send notifications via e-mail
- `slack` to send notifications through a Slack webhook
From 9389e8f34ff33a05daa044249987b380575cf291 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Tue, 26 Nov 2019 19:39:55 +0100
Subject: [PATCH 204/761] update instruction on multiple notifications
closes #412
---
docs/notifications.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index a731855..b95e95e 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -2,13 +2,14 @@
# Notifications
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
-The types of notifications to send are passed via the space-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
+The types of notifications to send are set by passing a comma-separated list of values to the `--notifications` option (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
- `email` to send notifications via e-mail
- `slack` to send notifications through a Slack webhook
- `msteams` to send notifications via MSTeams webhook
- `gotify` to send notifications via Gotify
+> There is currently a [bug](https://github.com/spf13/viper/issues/380) in Viper, which prevents comma-separated slices to be used when using the environment variable. A workaround is available where we instead put quotes around the environment variable value and replace the commas with spaces, as `WATCHTOWER_NOTIFICATIONS="slack msteams"`
> If you're a `docker-compose` user, make sure to specify environment variables' values in your `.yml` file without double quotes (`"`).
>
From 4f120ef037cb79db6895e20d542cd301dcbb3cc8 Mon Sep 17 00:00:00 2001
From: John Clayton
Date: Wed, 18 Dec 2019 16:40:02 -0700
Subject: [PATCH 205/761] =?UTF-8?q?Don=E2=80=99t=20delete=20cmd=20when=20r?=
=?UTF-8?q?untime=20entrypoint=20is=20different?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pkg/container/container.go | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 09e4225..a4db4cf 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -143,12 +143,11 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
config.User = ""
}
- if util.SliceEqual(config.Cmd, imageConfig.Cmd) {
- config.Cmd = nil
- }
-
if util.SliceEqual(config.Entrypoint, imageConfig.Entrypoint) {
config.Entrypoint = nil
+ if util.SliceEqual(config.Cmd, imageConfig.Cmd) {
+ config.Cmd = nil
+ }
}
config.Env = util.SliceSubtract(config.Env, imageConfig.Env)
From fa1b8d5d0c4ba2b2bd45fa4e0f92258beea25635 Mon Sep 17 00:00:00 2001
From: Lukas Elsner
Date: Thu, 19 Dec 2019 12:57:50 -0500
Subject: [PATCH 206/761] proper set implementation
---
internal/actions/update.go | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 4afa0f1..37fb22e 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -3,7 +3,6 @@ package actions
import (
"github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
- "github.com/deckarep/golang-set"
log "github.com/sirupsen/logrus"
)
@@ -74,18 +73,16 @@ func stopStaleContainer(container container.Container, client container.Client,
}
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
- toDelete := mapset.NewSet()
+ toDelete := make(map[container.Container]bool)
for _, container := range containers {
if !container.Stale {
continue
}
restartStaleContainer(container, client, params)
- toDelete.Add(container)
+ toDelete[container] = true
}
if params.Cleanup {
- iterator := toDelete.Iterator()
- for c := range iterator.C {
- cont := c.(container.Container)
+ for cont := range toDelete {
if err := client.RemoveImage(cont); err != nil {
log.Error(err)
}
From 0784aa5e49a0446b61bd88cff15f1fca0f9d4576 Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki
Date: Wed, 25 Dec 2019 10:40:35 +0100
Subject: [PATCH 207/761] Fixed wrong filename in documentation
---
docs/credential-helpers.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
index 1722906..165a9cd 100644
--- a/docs/credential-helpers.md
+++ b/docs/credential-helpers.md
@@ -45,7 +45,7 @@ volumes:
helper: {}
```
-and for `.docker/config.yml`:
+and for `.docker/config.json`:
```yaml
{
"HttpHeaders" : {
@@ -61,4 +61,4 @@ and for `.docker/config.yml`:
"index.docker.io": "osxkeychain" // ...or your prefered helper
}
}
-```
\ No newline at end of file
+```
From 3b8305985fda16e03d580df8539cbb4b9b91cf96 Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki
Date: Wed, 25 Dec 2019 10:47:57 +0100
Subject: [PATCH 208/761] Added full path to config file
---
docs/credential-helpers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
index 165a9cd..d1869e1 100644
--- a/docs/credential-helpers.md
+++ b/docs/credential-helpers.md
@@ -45,7 +45,7 @@ volumes:
helper: {}
```
-and for `.docker/config.json`:
+and for `/.docker/config.json`:
```yaml
{
"HttpHeaders" : {
From 35be6ce37f1458facbe59ca78e602cfa4865b159 Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki
Date: Wed, 25 Dec 2019 13:29:38 +0100
Subject: [PATCH 209/761] Changed codeblock language
---
docs/credential-helpers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
index d1869e1..719d11e 100644
--- a/docs/credential-helpers.md
+++ b/docs/credential-helpers.md
@@ -46,7 +46,7 @@ volumes:
```
and for `/.docker/config.json`:
-```yaml
+```json
{
"HttpHeaders" : {
"User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
From f28093e0eabcc09de3baf909547e5004343a2700 Mon Sep 17 00:00:00 2001
From: Jan Kristof Nidzwetzki
Date: Wed, 25 Dec 2019 13:32:39 +0100
Subject: [PATCH 210/761] Update credential-helpers.md
---
docs/credential-helpers.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
index 719d11e..cca9e92 100644
--- a/docs/credential-helpers.md
+++ b/docs/credential-helpers.md
@@ -51,14 +51,16 @@ and for `/.docker/config.json`:
"HttpHeaders" : {
"User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
},
- "credsStore" : "osxkeychain", // ...or your prefered helper
+ "credsStore" : "osxkeychain",
"auths" : {
"xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : {},
"https://index.docker.io/v1/": {}
},
"credHelpers": {
"xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : "ecr-login",
- "index.docker.io": "osxkeychain" // ...or your prefered helper
+ "index.docker.io": "osxkeychain"
}
}
```
+
+*Note:* `osxkeychain` can be changed to your prefered credentials helper
From 893fd035dd3ae479c5006b1babcbc0926d3b70fd Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki
Date: Wed, 25 Dec 2019 13:37:48 +0100
Subject: [PATCH 211/761] Fixed small typo
---
docs/credential-helpers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
index cca9e92..c86ef7c 100644
--- a/docs/credential-helpers.md
+++ b/docs/credential-helpers.md
@@ -63,4 +63,4 @@ and for `/.docker/config.json`:
}
```
-*Note:* `osxkeychain` can be changed to your prefered credentials helper
+*Note:* `osxkeychain` can be changed to your prefered credentials helper.
From 32f70bd9af65ec3d4fb7cb0cbe6348f398bcc8ed Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 25 Dec 2019 12:41:04 +0000
Subject: [PATCH 212/761] docs: update README.md [skip ci]
---
README.md | 92 +++++++++++++++++++++++++++++--------------------------
1 file changed, 49 insertions(+), 43 deletions(-)
diff --git a/README.md b/README.md
index eb53ce9..558764c 100644
--- a/README.md
+++ b/README.md
@@ -62,64 +62,70 @@ The full documentation is available at https://containrrr.github.io/watchtower.
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
+
+
+
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
From e5a1814b7e816b061f9ba495b42b18de466b3296 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 25 Dec 2019 12:41:05 +0000
Subject: [PATCH 213/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index dfaa6e3..f24430c 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -393,6 +393,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "jnidzwetzki",
+ "name": "Jan Kristof Nidzwetzki",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/5753622?v=4",
+ "profile": "https://achfrag.net",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
@@ -400,5 +409,6 @@
"projectOwner": "containrrr",
"repoType": "github",
"repoHost": "https://github.com",
- "commitConvention": "none"
+ "commitConvention": "none",
+ "skipCi": true
}
From a447175adb3e9246ceae36da7c6c1340743af2f8 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 25 Dec 2019 12:41:55 +0000
Subject: [PATCH 214/761] docs: update README.md [skip ci]
---
README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 48 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 0b34aec..9c18264 100644
--- a/README.md
+++ b/README.md
@@ -59,9 +59,55 @@ The full documentation is available at https://containrrr.github.io/watchtower.
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
-
+
+
+
+
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
From dee5cb1c542cfffff8bb8c4fe7223f5514894e79 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 25 Dec 2019 12:41:56 +0000
Subject: [PATCH 215/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f57ce74..45af97f 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -299,7 +299,8 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/21138205?v=4",
"profile": "https://github.com/zoispag",
"contributions": [
- "code"
+ "code",
+ "review"
]
}
],
@@ -308,5 +309,6 @@
"projectOwner": "containrrr",
"repoType": "github",
"repoHost": "https://github.com",
- "commitConvention": "none"
+ "commitConvention": "none",
+ "skipCi": true
}
From 157ddb86a7385bd138da8c0b24eb0b42dad3840c Mon Sep 17 00:00:00 2001
From: Zois Pagoulatos
Date: Wed, 25 Dec 2019 16:07:11 +0100
Subject: [PATCH 216/761] #413 Change initial logging message from debug to
info
---
cmd/root.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/root.go b/cmd/root.go
index 453196a..265588d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -155,7 +155,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
return err
}
- log.Debug("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ log.Info("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
cron.Start()
// Graceful shut-down on SIGINT/SIGTERM
From 1ca1e20c7c750ebb634e6b3871abf0a9451ec693 Mon Sep 17 00:00:00 2001
From: Jan Nidzwetzki
Date: Wed, 25 Dec 2019 20:37:40 +0100
Subject: [PATCH 217/761] Renamed documentation file
---
docs/{credential-helpers.md => private-registries.md} | 0
mkdocs.yml | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename docs/{credential-helpers.md => private-registries.md} (100%)
diff --git a/docs/credential-helpers.md b/docs/private-registries.md
similarity index 100%
rename from docs/credential-helpers.md
rename to docs/private-registries.md
diff --git a/mkdocs.yml b/mkdocs.yml
index e5e7c34..645c1cc 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,7 +14,7 @@ nav:
- 'Arguments': 'arguments.md'
- 'Notifications': 'notifications.md'
- 'Container selection': 'container-selection.md'
- - 'Credential helpers': 'credential-helpers.md'
+ - 'Private registries': 'private-registries.md'
- 'Linked containers': 'linked-containers.md'
- 'Remote hosts': 'remote-hosts.md'
- 'Secure connections': 'secure-connections.md'
From 0c3133f2d05c3d0af79f57b56945af3b85126cbc Mon Sep 17 00:00:00 2001
From: Jan Kristof Nidzwetzki
Date: Wed, 25 Dec 2019 21:58:19 +0100
Subject: [PATCH 218/761] Documented private registries
---
docs/private-registries.md | 54 +++++++++++++++++++++++++++++++++++++-
1 file changed, 53 insertions(+), 1 deletion(-)
diff --git a/docs/private-registries.md b/docs/private-registries.md
index c86ef7c..4136c48 100644
--- a/docs/private-registries.md
+++ b/docs/private-registries.md
@@ -1,4 +1,56 @@
-Some private docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
+Watchtower supports private Docker image registries. In many cases, accessing a private registry requires a valid username and password (i.e., _credentials_). In order to operate in such an environment, watchtower needs to know the credentials to access the registry.
+
+The credentials can be provided to watchtower in a configuration file called `config.json`. There are two ways to generate this configuration file:
+
+* The configuration file can be created manually.
+* Call `docker login $REGISTRY_NAME` and share the resulting configuration file.
+
+### Create the configuration file manually
+Create a new configuration file with the following syntax and a base64 encoded username and password `auth` string:
+```json
+{
+ "auths": {
+ "$REGISTRY_NAME": {
+ "auth": "XXXXXXX"
+ }
+ }
+}
+```
+
+`$REGISTRY_NAME` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
+
+The required `auth` string can be generated as follows:
+```bash
+echo -n 'username:password' | base64
+```
+
+When the watchtower Docker container is stared, the created configuration file (`/config.json` in this example) needs to be passed to the container:
+```bash
+docker run [...] -v /config.json:/config.json containrrr/watchtower
+```
+
+### Share the Docker configuration file
+To pull an image from a private registry, `docker login` needs to be called first, to get access to the registry. The provided credentials are stored in a configuration file called `/.docker/config.json`. This configuration file can be directly used by watchtower. In this case, the creation of an additional configuration file is not necessary.
+
+When the Docker container is started, pass the configuration file to watchtower:
+```bash
+docker run [...] -v /.docker/config.json:/config.json containrrr/watchtower
+```
+
+When creating the watchtower container via docker-compose, use the following lines:
+```yaml
+version: "3"
+[...]
+watchtower:
+ image: index.docker.io/containrrr/watchtower:latest
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /.docker/config.json:/config.json
+[...]
+```
+
+## Credential helpers
+Some private Docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
To be able to use this together with watchtower, we need to use a credential helper.
To keep the image size small we've decided to not include any helpers in the watchtower image, instead we'll put the
From 8f8da6a2f4fa6a297c54457a09c351695bc419ff Mon Sep 17 00:00:00 2001
From: Jan Kristof Nidzwetzki
Date: Wed, 25 Dec 2019 21:59:10 +0100
Subject: [PATCH 219/761] Update private-registries.md
---
docs/private-registries.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/private-registries.md b/docs/private-registries.md
index 4136c48..4afbb86 100644
--- a/docs/private-registries.md
+++ b/docs/private-registries.md
@@ -3,21 +3,21 @@ Watchtower supports private Docker image registries. In many cases, accessing a
The credentials can be provided to watchtower in a configuration file called `config.json`. There are two ways to generate this configuration file:
* The configuration file can be created manually.
-* Call `docker login $REGISTRY_NAME` and share the resulting configuration file.
+* Call `docker login ` and share the resulting configuration file.
### Create the configuration file manually
Create a new configuration file with the following syntax and a base64 encoded username and password `auth` string:
```json
{
"auths": {
- "$REGISTRY_NAME": {
+ "": {
"auth": "XXXXXXX"
}
}
}
```
-`$REGISTRY_NAME` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
+`` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
The required `auth` string can be generated as follows:
```bash
From a79845703c94aad96c89e6d5392d212eb6c2f19e Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Thu, 26 Dec 2019 08:16:18 +0100
Subject: [PATCH 220/761] Update private-registries.md
---
docs/private-registries.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/private-registries.md b/docs/private-registries.md
index 4afbb86..13e7618 100644
--- a/docs/private-registries.md
+++ b/docs/private-registries.md
@@ -24,7 +24,7 @@ The required `auth` string can be generated as follows:
echo -n 'username:password' | base64
```
-When the watchtower Docker container is stared, the created configuration file (`/config.json` in this example) needs to be passed to the container:
+When the watchtower Docker container is started, the created configuration file (`/config.json` in this example) needs to be passed to the container:
```bash
docker run [...] -v /config.json:/config.json containrrr/watchtower
```
From c4f1d2e6df0c6fd321e65f7379f9948e8a8347d0 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Dec 2019 09:51:12 +0000
Subject: [PATCH 221/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 37a06a8..ca393e2 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Jan Kristof Nidzwetzki đ |
+  lukas đģ |
From 3d23186d381f1fb2ffa8cb69dcb70fb1d371bc71 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Dec 2019 09:51:13 +0000
Subject: [PATCH 222/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 043cacc..1d2f240 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -403,6 +403,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "mindrunner",
+ "name": "lukas",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/1413542?v=4",
+ "profile": "https://www.lukaselsner.de",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From 545c76f0111f542f24c2d2ee6c1aa5ecf57ee896 Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Tue, 7 Jan 2020 13:59:19 -0300
Subject: [PATCH 223/761] Adds self-contained dev Dockerfile
---
Dockerfile.dev-self-contained | 38 +++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 Dockerfile.dev-self-contained
diff --git a/Dockerfile.dev-self-contained b/Dockerfile.dev-self-contained
new file mode 100644
index 0000000..307ffbe
--- /dev/null
+++ b/Dockerfile.dev-self-contained
@@ -0,0 +1,38 @@
+#
+# Builder
+#
+
+FROM golang:alpine as builder
+
+# use version (for example "v0.3.3") or "master"
+ARG WATCHTOWER_VERSION=master
+
+RUN apk add --no-cache \
+ alpine-sdk \
+ ca-certificates \
+ git \
+ tzdata
+
+COPY . /watchtower
+
+RUN \
+ cd /watchtower && \
+ \
+ GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' . && \
+ GO111MODULE=on go test ./... -v
+
+
+#
+# watchtower
+#
+
+FROM scratch
+
+LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other container
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=builder /watchtower/watchtower /watchtower
+
+ENTRYPOINT ["/watchtower"]
From 503d6933773d2ba34bdffa23bab0f896c8e3a97f Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Wed, 8 Jan 2020 18:51:04 -0300
Subject: [PATCH 224/761] Moved dockerfiles to an exclusive folder
---
Dockerfile => dockerfiles/Dockerfile | 0
.../Dockerfile.dev-self-contained | 0
.../Dockerfile.self-contained | 0
3 files changed, 0 insertions(+), 0 deletions(-)
rename Dockerfile => dockerfiles/Dockerfile (100%)
rename Dockerfile.dev-self-contained => dockerfiles/Dockerfile.dev-self-contained (100%)
rename Dockerfile.self-contained => dockerfiles/Dockerfile.self-contained (100%)
diff --git a/Dockerfile b/dockerfiles/Dockerfile
similarity index 100%
rename from Dockerfile
rename to dockerfiles/Dockerfile
diff --git a/Dockerfile.dev-self-contained b/dockerfiles/Dockerfile.dev-self-contained
similarity index 100%
rename from Dockerfile.dev-self-contained
rename to dockerfiles/Dockerfile.dev-self-contained
diff --git a/Dockerfile.self-contained b/dockerfiles/Dockerfile.self-contained
similarity index 100%
rename from Dockerfile.self-contained
rename to dockerfiles/Dockerfile.self-contained
From 85d39ddcd11824ad7fc1e316b028a5b8b93be820 Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Wed, 8 Jan 2020 19:42:08 -0300
Subject: [PATCH 225/761] Edits goreleaser to match exclusive dockerfiles
folder
---
goreleaser.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/goreleaser.yml b/goreleaser.yml
index 4fa3841..927cdcd 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -29,7 +29,7 @@ dockers:
goos: linux
goarch: amd64
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:amd64-{{ .Version }}
- containrrr/watchtower:amd64-latest
@@ -39,7 +39,7 @@ dockers:
goos: linux
goarch: 386
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:i386-{{ .Version }}
- containrrr/watchtower:i386-latest
@@ -49,7 +49,7 @@ dockers:
goos: linux
goarch: arm
goarm: 6
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:armhf-{{ .Version }}
- containrrr/watchtower:armhf-latest
@@ -59,7 +59,7 @@ dockers:
goos: linux
goarch: arm64
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:arm64v8-{{ .Version }}
- containrrr/watchtower:arm64v8-latest
From 798f48aab8f3bf80aab7a1a9770a87078d592905 Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Wed, 8 Jan 2020 20:02:47 -0300
Subject: [PATCH 226/761] Adds development self-contained builds instructions
to CONTRIBUTING.md
---
CONTRIBUTING.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index db50329..82b1b1b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,4 +25,14 @@ go build # compiles and packages an executable bin
go test ./... -v # runs tests with verbose output
./watchtower # runs the application (outside of a container)
```
+
If you dont have it enabled, you'll either have to prefix each command with `GO111MODULE=on` or run `export GO111MODULE=on` before running the commands. [You can read more about modules here.](https://github.com/golang/go/wiki/Modules)
+
+To build a Watchtower image of your own, use the self-contained Dockerfiles. As the main Dockerfile, they can be found in `dockerfiles/`:
+- `dockerfiles/Dockerfile.dev-self-contained` will build an image based on your current local Watchtower files.
+- `dockerfiles/Dockerfile.self-contained` will build an image based on current Watchtower's repository on GitHub.
+
+e.g.:
+```bash
+sudo docker build . -f dockerfiles/Dockerfile.dev-self-contained -t containrrr/watchtower # to build an image from local files
+```
\ No newline at end of file
From 4533cae8328589336d3d50629b6d1e58de62630a Mon Sep 17 00:00:00 2001
From: Ameya Shenoy
Date: Fri, 10 Jan 2020 01:17:42 +0530
Subject: [PATCH 227/761] =?UTF-8?q?fix:=20=F0=9F=90=9B=20bump=20alpine=20v?=
=?UTF-8?q?ersion?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes: #400
Signed-off-by: Ameya Shenoy
---
Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 75f70f2..f792d32 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.9 as alpine
+FROM alpine:3.11 as alpine
RUN apk add --no-cache \
ca-certificates \
@@ -15,4 +15,4 @@ COPY --from=alpine \
/usr/share/zoneinfo
COPY watchtower /
-ENTRYPOINT ["/watchtower"]
\ No newline at end of file
+ENTRYPOINT ["/watchtower"]
From 8b16f6d766877b0ec5ec2399e27578ad11a64a55 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 9 Jan 2020 19:59:18 +0000
Subject: [PATCH 228/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index ca393e2..1847df5 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Jan Kristof Nidzwetzki đ |
 lukas đģ |
+  Ameya Shenoy đģ |
From 1e7296ae48bd52dc5a1452baab1d66140f2a20ba Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 9 Jan 2020 19:59:19 +0000
Subject: [PATCH 229/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 1d2f240..957507c 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -412,6 +412,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "codingCoffee",
+ "name": "Ameya Shenoy",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/13611153?v=4",
+ "profile": "https://codingcoffee.dev",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From 15a92816248ccecab91ac838fc1d1702a4a71278 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 10 Jan 2020 22:30:52 +0100
Subject: [PATCH 230/761] feat: add support for multiple email recipients
---
pkg/notifications/email.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index ca54499..fe4ca03 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"
"net/smtp"
"os"
+ "strings"
"time"
t "github.com/containrrr/watchtower/pkg/types"
@@ -113,7 +114,7 @@ func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
if e.User != "" {
auth = smtp.PlainAuth("", e.User, e.Password, e.Server)
}
- err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, []string{e.To}, msg)
+ err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, strings.Split(e.To, ","), msg)
if err != nil {
// Use fmt so it doesn't trigger another email.
fmt.Println("Failed to send notification email: ", err)
From 7b8b8e8ad95411011d15701d560300437ae72b73 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 00:28:27 +0100
Subject: [PATCH 231/761] #387 fix: switch to image id map and add additional
tests
---
go.mod | 6 +-
go.sum | 2 +
internal/actions/actions_suite_test.go | 124 +++++++------------------
internal/actions/check.go | 2 +-
internal/actions/mocks/client.go | 72 ++++++++++++++
internal/actions/mocks/container.go | 28 ++++++
internal/actions/update.go | 9 +-
internal/actions/update_test.go | 84 +++++++++++++++++
pkg/container/client.go | 19 ++--
9 files changed, 244 insertions(+), 102 deletions(-)
create mode 100644 internal/actions/mocks/client.go
create mode 100644 internal/actions/mocks/container.go
create mode 100644 internal/actions/update_test.go
diff --git a/go.mod b/go.mod
index a45b228..9bdd550 100644
--- a/go.mod
+++ b/go.mod
@@ -53,11 +53,11 @@ require (
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/zmap/zlint v1.0.2 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
+ golang.org/x/sys v0.0.0-20190412213103-97732733099d
+ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107
+ google.golang.org/grpc v1.21.0
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
- golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
- google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
- google.golang.org/grpc v1.21.0
gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index 160a1c2..f96232a 100644
--- a/go.sum
+++ b/go.sum
@@ -298,6 +298,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@@ -319,6 +320,7 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index 76d2be5..2c9b0c8 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -1,18 +1,16 @@
package actions_test
import (
- "errors"
"github.com/containrrr/watchtower/internal/actions"
"testing"
"time"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
- "github.com/docker/docker/api/types"
- t "github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client"
+ . "github.com/containrrr/watchtower/internal/actions/mocks"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
@@ -24,7 +22,7 @@ func TestActions(t *testing.T) {
var _ = Describe("the actions package", func() {
var dockerClient cli.CommonAPIClient
- var client mockClient
+ var client MockClient
BeforeSuite(func() {
server := mocks.NewMockAPIServer()
dockerClient, _ = cli.NewClientWithOpts(
@@ -32,12 +30,15 @@ var _ = Describe("the actions package", func() {
cli.WithHTTPClient(server.Client()))
})
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{},
- }
+ pullImages := false
+ removeVolumes := false
+
+ client = CreateMockClient(
+ &TestData {},
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
Describe("the check prerequisites method", func() {
@@ -51,7 +52,7 @@ var _ = Describe("the actions package", func() {
When("given an array of one", func() {
It("should not do anything", func() {
client.TestData.Containers = []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container",
"test-container",
"watchtower",
@@ -63,27 +64,30 @@ var _ = Describe("the actions package", func() {
})
When("given multiple containers", func() {
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{
+ pullImages := false
+ removeVolumes := false
+ client = CreateMockClient(
+ &TestData{
NameOfContainerToKeep: "test-container-02",
Containers: []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
- createMockContainer(
+ CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
},
},
- }
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
+
It("should stop all but the latest one", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
@@ -91,96 +95,40 @@ var _ = Describe("the actions package", func() {
})
When("deciding whether to cleanup images", func() {
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{
+ pullImages := false
+ removeVolumes := false
+
+ client = CreateMockClient(
+ &TestData{
Containers: []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
- createMockContainer(
+ CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
},
},
- }
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
It("should try to delete the image if the cleanup flag is true", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, true)
Expect(err).NotTo(HaveOccurred())
- Expect(client.TestData.TriedToRemoveImage).To(BeTrue())
+ Expect(client.TestData.TriedToRemoveImage()).To(BeTrue())
})
It("should not try to delete the image if the cleanup flag is false", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
- Expect(client.TestData.TriedToRemoveImage).To(BeFalse())
+ Expect(client.TestData.TriedToRemoveImage()).To(BeFalse())
})
})
})
})
-func createMockContainer(id string, name string, image string, created time.Time) container.Container {
- content := types.ContainerJSON{
- ContainerJSONBase: &types.ContainerJSONBase{
- ID: id,
- Image: image,
- Name: name,
- Created: created.String(),
- },
- }
- return *container.NewContainer(&content, nil)
-}
-
-type mockClient struct {
- TestData *TestData
- api cli.CommonAPIClient
- pullImages bool
- removeVolumes bool
-}
-
-type TestData struct {
- TriedToRemoveImage bool
- NameOfContainerToKeep string
- Containers []container.Container
-}
-
-func (client mockClient) ListContainers(f t.Filter) ([]container.Container, error) {
- return client.TestData.Containers, nil
-}
-
-func (client mockClient) StopContainer(c container.Container, d time.Duration) error {
- if c.Name() == client.TestData.NameOfContainerToKeep {
- return errors.New("tried to stop the instance we want to keep")
- }
- return nil
-}
-func (client mockClient) StartContainer(c container.Container) (string, error) {
- panic("Not implemented")
-}
-
-func (client mockClient) RenameContainer(c container.Container, s string) error {
- panic("Not implemented")
-}
-
-func (client mockClient) RemoveImage(c container.Container) error {
- client.TestData.TriedToRemoveImage = true
- return nil
-}
-
-func (client mockClient) GetContainer(containerID string) (container.Container, error) {
- return container.Container{}, nil
-}
-
-func (client mockClient) ExecuteCommand(containerID string, command string) error {
- return nil
-}
-
-func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
- panic("Not implemented")
-}
diff --git a/internal/actions/check.go b/internal/actions/check.go
index 8574300..b60aec5 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -51,7 +51,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
}
if cleanup {
- if err := client.RemoveImage(c); err != nil {
+ if err := client.RemoveImageByID(c.ImageID()); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
cleanupErrors++
diff --git a/internal/actions/mocks/client.go b/internal/actions/mocks/client.go
new file mode 100644
index 0000000..e01043a
--- /dev/null
+++ b/internal/actions/mocks/client.go
@@ -0,0 +1,72 @@
+package mocks
+
+import (
+ "errors"
+ "github.com/containrrr/watchtower/pkg/container"
+ "time"
+
+ t "github.com/containrrr/watchtower/pkg/types"
+ cli "github.com/docker/docker/client"
+)
+
+type MockClient struct {
+ TestData *TestData
+ api cli.CommonAPIClient
+ pullImages bool
+ removeVolumes bool
+}
+
+type TestData struct {
+ TriedToRemoveImageCount int
+ NameOfContainerToKeep string
+ Containers []container.Container
+}
+
+func (testdata *TestData) TriedToRemoveImage() bool {
+ return testdata.TriedToRemoveImageCount > 0
+}
+
+func CreateMockClient(data *TestData, api cli.CommonAPIClient, pullImages bool, removeVolumes bool) MockClient {
+ return MockClient {
+ data,
+ api,
+ pullImages,
+ removeVolumes,
+ }
+}
+
+func (client MockClient) ListContainers(f t.Filter) ([]container.Container, error) {
+ return client.TestData.Containers, nil
+}
+
+func (client MockClient) StopContainer(c container.Container, d time.Duration) error {
+ if c.Name() == client.TestData.NameOfContainerToKeep {
+ return errors.New("tried to stop the instance we want to keep")
+ }
+ return nil
+}
+func (client MockClient) StartContainer(c container.Container) (string, error) {
+ return "", nil
+}
+
+func (client MockClient) RenameContainer(c container.Container, s string) error {
+ return nil
+}
+
+func (client MockClient) RemoveImageByID(id string) error {
+ client.TestData.TriedToRemoveImageCount++
+ return nil
+}
+
+func (client MockClient) GetContainer(containerID string) (container.Container, error) {
+ return container.Container{}, nil
+}
+
+func (client MockClient) ExecuteCommand(containerID string, command string) error {
+ return nil
+}
+
+func (client MockClient) IsContainerStale(c container.Container) (bool, error) {
+ return true, nil
+}
+
diff --git a/internal/actions/mocks/container.go b/internal/actions/mocks/container.go
new file mode 100644
index 0000000..4a56106
--- /dev/null
+++ b/internal/actions/mocks/container.go
@@ -0,0 +1,28 @@
+package mocks
+
+import (
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/docker/docker/api/types"
+ container2 "github.com/docker/docker/api/types/container"
+ "time"
+)
+
+func CreateMockContainer(id string, name string, image string, created time.Time) container.Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: id,
+ Image: image,
+ Name: name,
+ Created: created.String(),
+ },
+ Config: &container2.Config{
+ Labels: make(map[string]string),
+ },
+ }
+ return *container.NewContainer(
+ &content,
+ &types.ImageInspect{
+ ID: image,
+ },
+ )
+}
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 37fb22e..5461f35 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -73,17 +73,18 @@ func stopStaleContainer(container container.Container, client container.Client,
}
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
- toDelete := make(map[container.Container]bool)
+ imageIDs := make(map[string]bool)
+
for _, container := range containers {
if !container.Stale {
continue
}
restartStaleContainer(container, client, params)
- toDelete[container] = true
+ imageIDs[container.ImageID()] = true
}
if params.Cleanup {
- for cont := range toDelete {
- if err := client.RemoveImage(cont); err != nil {
+ for imageID := range imageIDs {
+ if err := client.RemoveImageByID(imageID); err != nil {
log.Error(err)
}
}
diff --git a/internal/actions/update_test.go b/internal/actions/update_test.go
new file mode 100644
index 0000000..4ab6c8a
--- /dev/null
+++ b/internal/actions/update_test.go
@@ -0,0 +1,84 @@
+package actions_test
+
+import (
+ "github.com/containrrr/watchtower/internal/actions"
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/container/mocks"
+ cli "github.com/docker/docker/client"
+ "time"
+
+ . "github.com/containrrr/watchtower/internal/actions/mocks"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+
+var _ = Describe("the update action", func() {
+ var dockerClient cli.CommonAPIClient
+ var client MockClient
+
+ BeforeEach(func() {
+ server := mocks.NewMockAPIServer()
+ dockerClient, _ = cli.NewClientWithOpts(
+ cli.WithHost(server.URL),
+ cli.WithHTTPClient(server.Client()))
+ })
+
+
+ When("watchtower has been instructed to clean up", func() {
+ BeforeEach(func() {
+ pullImages := false
+ removeVolumes := false
+ client = CreateMockClient(
+ &TestData{
+ NameOfContainerToKeep: "test-container-02",
+ Containers: []container.Container{
+ CreateMockContainer(
+ "test-container-01",
+ "test-container-01",
+ "fake-image:latest",
+ time.Now().AddDate(0, 0, -1)),
+ CreateMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "fake-image:latest",
+ time.Now()),
+ CreateMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "fake-image:latest",
+ time.Now()),
+ },
+ },
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
+ })
+
+ When("there are multiple containers using the same image", func() {
+ It("should only try to remove the image once", func() {
+
+ err := actions.Update(client, actions.UpdateParams{ Cleanup: true })
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
+ })
+ })
+ When("there are multiple containers using different images", func() {
+ It("should try to remove each of them", func() {
+ client.TestData.Containers = append(
+ client.TestData.Containers,
+ CreateMockContainer(
+ "unique-test-container",
+ "unique-test-container",
+ "unique-fake-image:latest",
+ time.Now(),
+ ),
+ )
+ err := actions.Update(client, actions.UpdateParams{ Cleanup: true })
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
+ })
+ })
+ })
+})
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 5877eb4..ab69d40 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -29,7 +29,8 @@ type Client interface {
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
ExecuteCommand(containerID string, command string) error
- RemoveImage(Container) error
+ RemoveImageByID(string) error
+
}
// NewClient returns a new Client instance which can be used to interact with
@@ -156,7 +157,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
// Wait for container to be removed. In this case an error is a good thing
if err := client.waitForStopOrTimeout(c, timeout); err == nil {
- return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
+ return fmt.Errorf("container %s (%s) could not be removed", c.Name(), c.ID())
}
return nil
@@ -279,10 +280,16 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return false, nil
}
-func (client dockerClient) RemoveImage(c Container) error {
- imageID := c.ImageID()
- log.Infof("Removing image %s", imageID)
- _, err := client.api.ImageRemove(context.Background(), imageID, types.ImageRemoveOptions{Force: true})
+func (client dockerClient) RemoveImageByID(id string) error {
+ log.Infof("Removing image %s", id)
+
+ _, err := client.api.ImageRemove(
+ context.Background(),
+ id,
+ types.ImageRemoveOptions{
+ Force: true,
+ })
+
return err
}
From 8aad258408eb2bc895bc4a275a2a8a724d610b1a Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 00:34:52 +0100
Subject: [PATCH 232/761] #387 fix: add comments to pass linting
---
internal/actions/mocks/client.go | 14 +++++++++++++-
internal/actions/mocks/container.go | 1 +
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/internal/actions/mocks/client.go b/internal/actions/mocks/client.go
index e01043a..dad2506 100644
--- a/internal/actions/mocks/client.go
+++ b/internal/actions/mocks/client.go
@@ -9,6 +9,7 @@ import (
cli "github.com/docker/docker/client"
)
+// MockClient is a mock that passes as a watchtower Client
type MockClient struct {
TestData *TestData
api cli.CommonAPIClient
@@ -16,16 +17,19 @@ type MockClient struct {
removeVolumes bool
}
+// TestData is the data used to perform the test
type TestData struct {
TriedToRemoveImageCount int
NameOfContainerToKeep string
Containers []container.Container
}
+// TriedToRemoveImage is a test helper function to check whether RemoveImageByID has been called
func (testdata *TestData) TriedToRemoveImage() bool {
return testdata.TriedToRemoveImageCount > 0
}
+// CreateMockClient creates a mock watchtower Client for usage in tests
func CreateMockClient(data *TestData, api cli.CommonAPIClient, pullImages bool, removeVolumes bool) MockClient {
return MockClient {
data,
@@ -35,37 +39,45 @@ func CreateMockClient(data *TestData, api cli.CommonAPIClient, pullImages bool,
}
}
+// ListContainers is a mock method returning the provided container testdata
func (client MockClient) ListContainers(f t.Filter) ([]container.Container, error) {
return client.TestData.Containers, nil
}
+// StopContainer is a mock method
func (client MockClient) StopContainer(c container.Container, d time.Duration) error {
if c.Name() == client.TestData.NameOfContainerToKeep {
return errors.New("tried to stop the instance we want to keep")
}
return nil
}
+
+// StartContainer is a mock method
func (client MockClient) StartContainer(c container.Container) (string, error) {
return "", nil
}
-
+// RenameContainer is a mock method
func (client MockClient) RenameContainer(c container.Container, s string) error {
return nil
}
+// RemoveImageByID increments the TriedToRemoveImageCount on being called
func (client MockClient) RemoveImageByID(id string) error {
client.TestData.TriedToRemoveImageCount++
return nil
}
+// GetContainer is a mock method
func (client MockClient) GetContainer(containerID string) (container.Container, error) {
return container.Container{}, nil
}
+// ExecuteCommand is a mock method
func (client MockClient) ExecuteCommand(containerID string, command string) error {
return nil
}
+// IsContainerStale is always true for the mock client
func (client MockClient) IsContainerStale(c container.Container) (bool, error) {
return true, nil
}
diff --git a/internal/actions/mocks/container.go b/internal/actions/mocks/container.go
index 4a56106..060340e 100644
--- a/internal/actions/mocks/container.go
+++ b/internal/actions/mocks/container.go
@@ -7,6 +7,7 @@ import (
"time"
)
+// CreateMockContainer creates a container substitute valid for testing
func CreateMockContainer(id string, name string, image string, created time.Time) container.Container {
content := types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
From 2c39d2954b2be09a7ea6dfa892217aa5c7fd4b72 Mon Sep 17 00:00:00 2001
From: Zois Pagoulatos
Date: Sat, 11 Jan 2020 17:58:02 +0100
Subject: [PATCH 233/761] Add github action to build and publish dev image to
dockerhub
---
.../workflows/publish-dev-dockerimage.yaml | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 .github/workflows/publish-dev-dockerimage.yaml
diff --git a/.github/workflows/publish-dev-dockerimage.yaml b/.github/workflows/publish-dev-dockerimage.yaml
new file mode 100644
index 0000000..a94a65c
--- /dev/null
+++ b/.github/workflows/publish-dev-dockerimage.yaml
@@ -0,0 +1,24 @@
+name: Docker image (latest-dev)
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: jerray/publish-docker-action@master
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_PASSWORD }}
+ path: ./dockerfiles/
+ file: Dockerfile.self-contained
+ repository: containrrr/watchtower
+ tags: latest-dev
From 4130b110c61de43738dcb2f03b406f3f15380c09 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 22:20:40 +0100
Subject: [PATCH 234/761] Update publish-dev-dockerimage.yaml
---
.github/workflows/publish-dev-dockerimage.yaml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/publish-dev-dockerimage.yaml b/.github/workflows/publish-dev-dockerimage.yaml
index a94a65c..c02186a 100644
--- a/.github/workflows/publish-dev-dockerimage.yaml
+++ b/.github/workflows/publish-dev-dockerimage.yaml
@@ -18,7 +18,6 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- path: ./dockerfiles/
- file: Dockerfile.self-contained
+ file: dockerfiles/Dockerfile.self-contained
repository: containrrr/watchtower
tags: latest-dev
From d1abce889a4b997b2e8a3978d7f49c7d44624393 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 23:35:25 +0100
Subject: [PATCH 235/761] refactor: extract code from the container package
---
cmd/root.go | 5 +-
internal/actions/check.go | 6 +-
internal/actions/update.go | 112 ++++--------------
internal/actions/update_test.go | 5 +-
pkg/container/client.go | 90 +++++++-------
pkg/container/container.go | 5 +
pkg/container/container_test.go | 9 +-
pkg/{container => filters}/filters.go | 26 ++--
pkg/{container => filters}/filters_test.go | 12 +-
pkg/lifecycle/lifecycle.go | 93 +++++++++++++++
pkg/registry/registry.go | 33 ++++++
pkg/{container => registry}/trust.go | 10 +-
pkg/{container => registry}/trust_test.go | 2 +-
pkg/{container => sorter}/sort.go | 25 ++--
.../actions => pkg/types}/update_params.go | 5 +-
15 files changed, 253 insertions(+), 185 deletions(-)
rename pkg/{container => filters}/filters.go (63%)
rename pkg/{container => filters}/filters_test.go (94%)
create mode 100644 pkg/lifecycle/lifecycle.go
create mode 100644 pkg/registry/registry.go
rename pkg/{container => registry}/trust.go (88%)
rename pkg/{container => registry}/trust_test.go (99%)
rename pkg/{container => sorter}/sort.go (71%)
rename {internal/actions => pkg/types}/update_params.go (74%)
diff --git a/cmd/root.go b/cmd/root.go
index 265588d..63a824c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "github.com/containrrr/watchtower/pkg/filters"
"os"
"os/signal"
"strconv"
@@ -108,7 +109,7 @@ func PreRun(cmd *cobra.Command, args []string) {
// Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) {
- filter := container.BuildFilter(names, enableLabel)
+ filter := filters.BuildFilter(names, enableLabel)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
if runOnce {
@@ -172,7 +173,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification()
- updateParams := actions.UpdateParams{
+ updateParams := t.UpdateParams{
Filter: filter,
Cleanup: cleanup,
NoRestart: noRestart,
diff --git a/internal/actions/check.go b/internal/actions/check.go
index b60aec5..785701f 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -3,6 +3,8 @@ package actions
import (
"errors"
"fmt"
+ "github.com/containrrr/watchtower/pkg/filters"
+ "github.com/containrrr/watchtower/pkg/sorter"
"sort"
"strings"
"time"
@@ -19,7 +21,7 @@ import (
// will stop and remove all but the most recently started container.
func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool) error {
awaitDockerClient()
- containers, err := client.ListContainers(container.WatchtowerContainersFilter)
+ containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
if err != nil {
log.Fatal(err)
@@ -39,7 +41,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
var cleanupErrors int
var stopErrors int
- sort.Sort(container.ByCreated(containers))
+ sort.Sort(sorter.ByCreated(containers))
allContainersExceptLast := containers[0 : len(containers)-1]
for _, c := range allContainersExceptLast {
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 5461f35..874e705 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -3,6 +3,9 @@ package actions
import (
"github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/lifecycle"
+ "github.com/containrrr/watchtower/pkg/sorter"
+ "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
@@ -10,10 +13,12 @@ import (
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
-func Update(client container.Client, params UpdateParams) error {
+func Update(client container.Client, params types.UpdateParams) error {
log.Debug("Checking containers for updated images")
- executePreCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePreChecks(client, params)
+ }
containers, err := client.ListContainers(params.Filter)
if err != nil {
@@ -30,7 +35,7 @@ func Update(client container.Client, params UpdateParams) error {
containers[i].Stale = stale
}
- containers, err = container.SortByDependencies(containers)
+ containers, err = sorter.SortByDependencies(containers)
if err != nil {
return err
}
@@ -38,24 +43,28 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers)
if params.MonitorOnly {
- executePostCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePostChecks(client, params)
+ }
return nil
}
stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params)
- executePostCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePostChecks(client, params)
+ }
return nil
}
-func stopContainersInReversedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
for i := len(containers) - 1; i >= 0; i-- {
stopStaleContainer(containers[i], client, params)
}
}
-func stopStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+func stopStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
if container.IsWatchtower() {
log.Debugf("This is the watchtower container %s", container.Name())
return
@@ -64,15 +73,17 @@ func stopStaleContainer(container container.Container, client container.Client,
if !container.Stale {
return
}
+ if params.LifecycleHooks {
+ lifecycle.ExecutePreUpdateCommand(client, container)
- executePreUpdateCommand(client, container)
+ }
if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
}
-func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
imageIDs := make(map[string]bool)
for _, container := range containers {
@@ -91,7 +102,7 @@ func restartContainersInSortedOrder(containers []container.Container, client con
}
}
-func restartStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
// Since we can't shutdown a watchtower container immediately, we need to
// start the new one while the old one is still running. This prevents us
// from re-using the same container name so we first rename the current
@@ -107,7 +118,7 @@ func restartStaleContainer(container container.Container, client container.Clien
if newContainerID, err := client.StartContainer(container); err != nil {
log.Error(err)
} else if container.Stale && params.LifecycleHooks {
- executePostUpdateCommand(client, newContainerID)
+ lifecycle.ExecutePostUpdateCommand(client, newContainerID)
}
}
}
@@ -130,82 +141,3 @@ func checkDependencies(containers []container.Container) {
}
}
}
-
-func executePreCheck(client container.Client, params UpdateParams) {
- containers, err := client.ListContainers(params.Filter)
- if err != nil {
- return
- }
- for _, container := range containers {
- executePreCheckCommand(client, container)
- }
-}
-
-func executePostCheck(client container.Client, params UpdateParams) {
- containers, err := client.ListContainers(params.Filter)
- if err != nil {
- return
- }
- for _, container := range containers {
- executePostCheckCommand(client, container)
- }
-}
-
-func executePreCheckCommand(client container.Client, container container.Container) {
- command := container.GetLifecyclePreCheckCommand()
- if len(command) == 0 {
- log.Debug("No pre-check command supplied. Skipping")
- return
- }
-
- log.Info("Executing pre-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePostCheckCommand(client container.Client, container container.Container) {
- command := container.GetLifecyclePostCheckCommand()
- if len(command) == 0 {
- log.Debug("No post-check command supplied. Skipping")
- return
- }
-
- log.Info("Executing post-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePreUpdateCommand(client container.Client, container container.Container) {
-
- command := container.GetLifecyclePreUpdateCommand()
- if len(command) == 0 {
- log.Debug("No pre-update command supplied. Skipping")
- return
- }
-
- log.Info("Executing pre-update command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePostUpdateCommand(client container.Client, newContainerID string) {
- newContainer, err := client.GetContainer(newContainerID)
- if err != nil {
- log.Error(err)
- return
- }
-
- command := newContainer.GetLifecyclePostUpdateCommand()
- if len(command) == 0 {
- log.Debug("No post-update command supplied. Skipping")
- return
- }
-
- log.Info("Executing post-update command.")
- if err := client.ExecuteCommand(newContainerID, command); err != nil {
- log.Error(err)
- }
-}
diff --git a/internal/actions/update_test.go b/internal/actions/update_test.go
index 4ab6c8a..3c9befe 100644
--- a/internal/actions/update_test.go
+++ b/internal/actions/update_test.go
@@ -4,6 +4,7 @@ import (
"github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
+ "github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client"
"time"
@@ -59,7 +60,7 @@ var _ = Describe("the update action", func() {
When("there are multiple containers using the same image", func() {
It("should only try to remove the image once", func() {
- err := actions.Update(client, actions.UpdateParams{ Cleanup: true })
+ err := actions.Update(client, types.UpdateParams{ Cleanup: true })
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
})
@@ -75,7 +76,7 @@ var _ = Describe("the update action", func() {
time.Now(),
),
)
- err := actions.Update(client, actions.UpdateParams{ Cleanup: true })
+ err := actions.Update(client, types.UpdateParams{ Cleanup: true })
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
})
diff --git a/pkg/container/client.go b/pkg/container/client.go
index ab69d40..607b84c 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -3,6 +3,7 @@ package container
import (
"bytes"
"fmt"
+ "github.com/containrrr/watchtower/pkg/registry"
"io/ioutil"
"strings"
"time"
@@ -12,7 +13,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
- dockerclient "github.com/docker/docker/client"
+ sdkClient "github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@@ -40,7 +41,7 @@ type Client interface {
// * DOCKER_TLS_VERIFY whether to verify tls certificates
// * DOCKER_API_VERSION the minimum docker api version to work with
func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool) Client {
- cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
+ cli, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv)
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
@@ -56,7 +57,7 @@ func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeV
}
type dockerClient struct {
- api dockerclient.CommonAPIClient
+ api sdkClient.CommonAPIClient
pullImages bool
removeVolumes bool
includeStopped bool
@@ -231,53 +232,60 @@ func (client dockerClient) RenameContainer(c Container, newName string) error {
return client.api.ContainerRename(bg, c.ID(), newName)
}
-func (client dockerClient) IsContainerStale(c Container) (bool, error) {
- bg := context.Background()
- oldImageInfo := c.imageInfo
- imageName := c.ImageName()
+func (client dockerClient) IsContainerStale(container Container) (bool, error) {
+ ctx := context.Background()
- if client.pullImages {
- log.Debugf("Pulling %s for %s", imageName, c.Name())
-
- var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
- auth, err := EncodedAuth(imageName)
- log.Debugf("Got auth value: %s", auth)
- log.Debugf("Got image name: %s", imageName)
- if err != nil {
- log.Debugf("Error loading authentication credentials %s", err)
- return false, err
- } else if auth == "" {
- log.Debugf("No authentication credentials found for %s", imageName)
- opts = types.ImagePullOptions{} // empty/no auth credentials
- } else {
- opts = types.ImagePullOptions{RegistryAuth: auth, PrivilegeFunc: DefaultAuthHandler}
- }
-
- response, err := client.api.ImagePull(bg, imageName, opts)
- if err != nil {
- log.Debugf("Error pulling image %s, %s", imageName, err)
- return false, err
- }
- defer response.Close()
-
- // the pull request will be aborted prematurely unless the response is read
- if _, err = ioutil.ReadAll(response); err != nil {
- log.Error(err)
- }
+ if !client.pullImages {
+ log.Debugf("Skipping image pull.")
+ } else if err := client.PullImage(ctx, container); err != nil {
+ return false, err
}
- newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
+ return client.HasNewImage(ctx, container)
+}
+
+func (client dockerClient) HasNewImage(ctx context.Context, container Container) (bool, error) {
+ oldImageID := container.imageInfo.ID
+ imageName := container.ImageName()
+
+ newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, 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
+ if newImageInfo.ID == oldImageID {
+ log.Debugf("No new images found for %s", container.Name())
+ return false, nil
}
- log.Debugf("No new images found for %s", c.Name())
- return false, nil
+ log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
+ return true, nil
+}
+
+func (client dockerClient) PullImage(ctx context.Context, container Container) error {
+ containerName := container.Name()
+ imageName := container.ImageName()
+ log.Debugf("Pulling %s for %s", imageName, containerName)
+
+ opts, err := registry.GetPullOptions(imageName)
+ if err != nil {
+ log.Debugf("Error loading authentication credentials %s", err)
+ return err
+ }
+
+ response, err := client.api.ImagePull(ctx, imageName, opts)
+ if err != nil {
+ log.Debugf("Error pulling image %s, %s", imageName, err)
+ return err
+ }
+
+ defer response.Close()
+ // the pull request will be aborted prematurely unless the response is read
+ if _, err = ioutil.ReadAll(response); err != nil {
+ log.Error(err)
+ return err
+ }
+ return nil
}
func (client dockerClient) RemoveImageByID(id string) error {
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 09e4225..99868f3 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -28,6 +28,11 @@ type Container struct {
imageInfo *types.ImageInspect
}
+// ContainerInfo fetches JSON info for the container
+func (c Container) ContainerInfo() *types.ContainerJSON {
+ return c.containerInfo
+}
+
// ID returns the Docker container ID.
func (c Container) ID() string {
return c.containerInfo.ID
diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go
index 9e1b213..fe838f7 100644
--- a/pkg/container/container_test.go
+++ b/pkg/container/container_test.go
@@ -2,6 +2,7 @@ package container
import (
"github.com/containrrr/watchtower/pkg/container/mocks"
+ "github.com/containrrr/watchtower/pkg/filters"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
cli "github.com/docker/docker/client"
@@ -34,14 +35,14 @@ var _ = Describe("the container", func() {
})
When("listing containers without any filter", func() {
It("should return all available containers", func() {
- containers, err := client.ListContainers(noFilter)
+ containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 2).To(BeTrue())
})
})
When("listing containers with a filter matching nothing", func() {
It("should return an empty array", func() {
- filter := filterByNames([]string{"lollercoaster"}, noFilter)
+ filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
containers, err := client.ListContainers(filter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 0).To(BeTrue())
@@ -49,7 +50,7 @@ var _ = Describe("the container", func() {
})
When("listing containers with a watchtower filter", func() {
It("should return only the watchtower container", func() {
- containers, err := client.ListContainers(WatchtowerContainersFilter)
+ containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 1).To(BeTrue())
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
@@ -62,7 +63,7 @@ var _ = Describe("the container", func() {
pullImages: false,
includeStopped: true,
}
- containers, err := client.ListContainers(noFilter)
+ containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) > 0).To(BeTrue())
})
diff --git a/pkg/container/filters.go b/pkg/filters/filters.go
similarity index 63%
rename from pkg/container/filters.go
rename to pkg/filters/filters.go
index b4d4911..b923745 100644
--- a/pkg/container/filters.go
+++ b/pkg/filters/filters.go
@@ -1,15 +1,15 @@
-package container
+package filters
import t "github.com/containrrr/watchtower/pkg/types"
// WatchtowerContainersFilter filters only watchtower containers
func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() }
-// Filter no containers and returns all
-func noFilter(t.FilterableContainer) bool { return true }
+// NoFilter will not filter out any containers
+func NoFilter(t.FilterableContainer) bool { return true }
-// Filters containers which don't have a specified name
-func filterByNames(names []string, baseFilter t.Filter) t.Filter {
+// FilterByNames returns all containers that match the specified name
+func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 {
return baseFilter
}
@@ -24,8 +24,8 @@ func filterByNames(names []string, baseFilter t.Filter) t.Filter {
}
}
-// Filters out containers that don't have the 'enableLabel'
-func filterByEnableLabel(baseFilter t.Filter) t.Filter {
+// FilterByEnableLabel returns all containers that have the enabled label set
+func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool {
// If label filtering is enabled, containers should only be considered
// if the label is specifically set.
@@ -38,8 +38,8 @@ func filterByEnableLabel(baseFilter t.Filter) t.Filter {
}
}
-// Filters out containers that have a 'enableLabel' and is set to disable.
-func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
+// FilterByDisabledLabel returns all containers that have the enabled label set to disable
+func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool {
enabledLabel, ok := c.Enabled()
if ok && !enabledLabel {
@@ -53,13 +53,13 @@ func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
// BuildFilter creates the needed filter of containers
func BuildFilter(names []string, enableLabel bool) t.Filter {
- filter := noFilter
- filter = filterByNames(names, filter)
+ filter := NoFilter
+ filter = FilterByNames(names, filter)
if enableLabel {
// If label filtering is enabled, containers should only be considered
// if the label is specifically set.
- filter = filterByEnableLabel(filter)
+ filter = FilterByEnableLabel(filter)
}
- filter = filterByDisabledLabel(filter)
+ filter = FilterByDisabledLabel(filter)
return filter
}
diff --git a/pkg/container/filters_test.go b/pkg/filters/filters_test.go
similarity index 94%
rename from pkg/container/filters_test.go
rename to pkg/filters/filters_test.go
index 4118335..d24b186 100644
--- a/pkg/container/filters_test.go
+++ b/pkg/filters/filters_test.go
@@ -1,4 +1,4 @@
-package container
+package filters
import (
"testing"
@@ -20,7 +20,7 @@ func TestWatchtowerContainersFilter(t *testing.T) {
func TestNoFilter(t *testing.T) {
container := new(mocks.FilterableContainer)
- assert.True(t, noFilter(container))
+ assert.True(t, NoFilter(container))
container.AssertExpectations(t)
}
@@ -28,12 +28,12 @@ func TestNoFilter(t *testing.T) {
func TestFilterByNames(t *testing.T) {
var names []string
- filter := filterByNames(names, nil)
+ filter := FilterByNames(names, nil)
assert.Nil(t, filter)
names = append(names, "test")
- filter = filterByNames(names, noFilter)
+ filter = FilterByNames(names, NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@@ -48,7 +48,7 @@ func TestFilterByNames(t *testing.T) {
}
func TestFilterByEnableLabel(t *testing.T) {
- filter := filterByEnableLabel(noFilter)
+ filter := FilterByEnableLabel(NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@@ -68,7 +68,7 @@ func TestFilterByEnableLabel(t *testing.T) {
}
func TestFilterByDisabledLabel(t *testing.T) {
- filter := filterByDisabledLabel(noFilter)
+ filter := FilterByDisabledLabel(NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go
new file mode 100644
index 0000000..9823f9d
--- /dev/null
+++ b/pkg/lifecycle/lifecycle.go
@@ -0,0 +1,93 @@
+package lifecycle
+
+import (
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/types"
+ log "github.com/sirupsen/logrus"
+)
+
+// ExecutePreChecks tries to run the pre-check lifecycle hook for all containers included by the current filter.
+func ExecutePreChecks(client container.Client, params types.UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ ExecutePreCheckCommand(client, container)
+ }
+}
+
+// ExecutePostChecks tries to run the post-check lifecycle hook for all containers included by the current filter.
+func ExecutePostChecks(client container.Client, params types.UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ ExecutePostCheckCommand(client, container)
+ }
+}
+
+// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
+func ExecutePreCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePreCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing pre-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
+func ExecutePostCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePostCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No post-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing post-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
+func ExecutePreUpdateCommand(client container.Client, container container.Container) {
+
+ command := container.GetLifecyclePreUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-update command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing pre-update command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePostUpdateCommand tries to run the post-update lifecycle hook for a single container.
+func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
+ newContainer, err := client.GetContainer(newContainerID)
+ if err != nil {
+ log.Error(err)
+ return
+ }
+
+ command := newContainer.GetLifecyclePostUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No post-update command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing post-update command.")
+ if err := client.ExecuteCommand(newContainerID, command); err != nil {
+ log.Error(err)
+ }
+}
+
diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go
new file mode 100644
index 0000000..2d7b9a8
--- /dev/null
+++ b/pkg/registry/registry.go
@@ -0,0 +1,33 @@
+package registry
+
+import (
+ "github.com/docker/docker/api/types"
+ log "github.com/sirupsen/logrus"
+)
+
+// GetPullOptions creates a struct with all options needed for pulling images from a registry
+func GetPullOptions(imageName string) (types.ImagePullOptions, error) {
+ auth, err := EncodedAuth(imageName)
+ log.Debugf("Got image name: %s", imageName)
+ if err != nil {
+ return types.ImagePullOptions{}, err
+ }
+
+ log.Debugf("Got auth value: %s", auth)
+ if auth == "" {
+ return types.ImagePullOptions{}, nil
+ }
+
+ return types.ImagePullOptions{
+ RegistryAuth: auth,
+ PrivilegeFunc: DefaultAuthHandler,
+ }, nil
+}
+
+// DefaultAuthHandler will be invoked if an AuthConfig is rejected
+// It could be used to return a new value for the "X-Registry-Auth" authentication header,
+// but there's no point trying again with the same value as used in AuthConfig
+func DefaultAuthHandler() (string, error) {
+ log.Debug("Authentication request was rejected. Trying again without authentication")
+ return "", nil
+}
diff --git a/pkg/container/trust.go b/pkg/registry/trust.go
similarity index 88%
rename from pkg/container/trust.go
rename to pkg/registry/trust.go
index 63b76a6..7403d46 100644
--- a/pkg/container/trust.go
+++ b/pkg/registry/trust.go
@@ -1,4 +1,4 @@
-package container
+package registry
import (
"errors"
@@ -97,11 +97,3 @@ func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
func EncodeAuth(auth types.AuthConfig) (string, error) {
return command.EncodeAuthToBase64(auth)
}
-
-// DefaultAuthHandler will be invoked if an AuthConfig is rejected
-// It could be used to return a new value for the "X-Registry-Auth" authentication header,
-// but there's no point trying again with the same value as used in AuthConfig
-func DefaultAuthHandler() (string, error) {
- log.Debug("Authentication request was rejected. Trying again without authentication")
- return "", nil
-}
diff --git a/pkg/container/trust_test.go b/pkg/registry/trust_test.go
similarity index 99%
rename from pkg/container/trust_test.go
rename to pkg/registry/trust_test.go
index 7d2ac96..8ffe1b9 100644
--- a/pkg/container/trust_test.go
+++ b/pkg/registry/trust_test.go
@@ -1,4 +1,4 @@
-package container
+package registry
import (
"github.com/stretchr/testify/assert"
diff --git a/pkg/container/sort.go b/pkg/sorter/sort.go
similarity index 71%
rename from pkg/container/sort.go
rename to pkg/sorter/sort.go
index 391a8b6..1e27f1b 100644
--- a/pkg/container/sort.go
+++ b/pkg/sorter/sort.go
@@ -1,13 +1,14 @@
-package container
+package sorter
import (
"fmt"
+ "github.com/containrrr/watchtower/pkg/container"
"time"
)
// ByCreated allows a list of Container structs to be sorted by the container's
// created date.
-type ByCreated []Container
+type ByCreated []container.Container
func (c ByCreated) Len() int { return len(c) }
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
@@ -15,12 +16,12 @@ func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
// Less will compare two elements (identified by index) in the Container
// list by created-date.
func (c ByCreated) Less(i, j int) bool {
- t1, err := time.Parse(time.RFC3339Nano, c[i].containerInfo.Created)
+ t1, err := time.Parse(time.RFC3339Nano, c[i].ContainerInfo().Created)
if err != nil {
t1 = time.Now()
}
- t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created)
+ t2, _ := time.Parse(time.RFC3339Nano, c[j].ContainerInfo().Created)
if err != nil {
t1 = time.Now()
}
@@ -33,18 +34,18 @@ func (c ByCreated) Less(i, j int) bool {
// the front of the list while containers with links will be sorted after all
// of their dependencies. This sort order ensures that linked containers can
// be started in the correct order.
-func SortByDependencies(containers []Container) ([]Container, error) {
+func SortByDependencies(containers []container.Container) ([]container.Container, error) {
sorter := dependencySorter{}
return sorter.Sort(containers)
}
type dependencySorter struct {
- unvisited []Container
+ unvisited []container.Container
marked map[string]bool
- sorted []Container
+ sorted []container.Container
}
-func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
+func (ds *dependencySorter) Sort(containers []container.Container) ([]container.Container, error) {
ds.unvisited = containers
ds.marked = map[string]bool{}
@@ -57,10 +58,10 @@ func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
return ds.sorted, nil
}
-func (ds *dependencySorter) visit(c Container) error {
+func (ds *dependencySorter) visit(c container.Container) error {
if _, ok := ds.marked[c.Name()]; ok {
- return fmt.Errorf("Circular reference to %s", c.Name())
+ return fmt.Errorf("circular reference to %s", c.Name())
}
// Mark any visited node so that circular references can be detected
@@ -83,7 +84,7 @@ func (ds *dependencySorter) visit(c Container) error {
return nil
}
-func (ds *dependencySorter) findUnvisited(name string) *Container {
+func (ds *dependencySorter) findUnvisited(name string) *container.Container {
for _, c := range ds.unvisited {
if c.Name() == name {
return &c
@@ -93,7 +94,7 @@ func (ds *dependencySorter) findUnvisited(name string) *Container {
return nil
}
-func (ds *dependencySorter) removeUnvisited(c Container) {
+func (ds *dependencySorter) removeUnvisited(c container.Container) {
var idx int
for i := range ds.unvisited {
if ds.unvisited[i].Name() == c.Name() {
diff --git a/internal/actions/update_params.go b/pkg/types/update_params.go
similarity index 74%
rename from internal/actions/update_params.go
rename to pkg/types/update_params.go
index ff586c6..8c6fea7 100644
--- a/internal/actions/update_params.go
+++ b/pkg/types/update_params.go
@@ -1,13 +1,12 @@
-package actions
+package types
import (
- t "github.com/containrrr/watchtower/pkg/types"
"time"
)
// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
- Filter t.Filter
+ Filter Filter
Cleanup bool
NoRestart bool
Timeout time.Duration
From 3fb299794b54175e5f06a6e12694651b596d5a4e Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 23:35:44 +0100
Subject: [PATCH 236/761] refactor: tidy up mod and sum files
---
go.mod | 4 ----
go.sum | 7 +++----
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/go.mod b/go.mod
index 9bdd550..2d2ced6 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,6 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
- github.com/deckarep/golang-set v1.7.1
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
@@ -53,9 +52,6 @@ require (
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/zmap/zlint v1.0.2 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
- golang.org/x/sys v0.0.0-20190412213103-97732733099d
- google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107
- google.golang.org/grpc v1.21.0
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
diff --git a/go.sum b/go.sum
index f96232a..230df62 100644
--- a/go.sum
+++ b/go.sum
@@ -15,7 +15,9 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -49,8 +51,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
-github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -298,7 +298,6 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@@ -320,13 +319,13 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
From af5d175e9f109cbf629642b9791123eefa09bde6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 11 Jan 2020 23:42:06 +0100
Subject: [PATCH 237/761] Update .all-contributorsrc
---
.all-contributorsrc | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 957507c..a8c4609 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -277,11 +277,12 @@
"login": "simskij",
"name": "Simon Aronsson",
"avatar_url": "https://avatars0.githubusercontent.com/u/1596025?v=4",
- "profile": "http://www.arcticbit.se",
+ "profile": "http://simme.dev",
"contributions": [
"code",
"maintenance",
- "review"
+ "review",
+ "doc"
]
},
{
From fde6e730770521919fec63d04bbe0b93dff846f4 Mon Sep 17 00:00:00 2001
From: Raymon de Looff
Date: Fri, 7 Feb 2020 21:23:49 +0100
Subject: [PATCH 238/761] Unset Hostname when NetworkMode is container
The hostname can not be set on containers using a container network stack
---
pkg/container/container.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 99868f3..a736873 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -138,6 +138,7 @@ func (c Container) StopSignal() string {
// the options overridden at runtime.
func (c Container) runtimeConfig() *dockercontainer.Config {
config := c.containerInfo.Config
+ hostConfig := c.containerInfo.HostConfig
imageConfig := c.imageInfo.Config
if config.WorkingDir == imageConfig.WorkingDir {
@@ -148,6 +149,10 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
config.User = ""
}
+ if hostConfig.NetworkMode.IsContainer() {
+ config.Hostname = ""
+ }
+
if util.SliceEqual(config.Cmd, imageConfig.Cmd) {
config.Cmd = nil
}
From 18b83789347882b8a767145b7159a81219b74f15 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 8 Feb 2020 13:36:51 +0000
Subject: [PATCH 239/761] docs: update README.md [skip ci]
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1847df5..6e42f6f 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Mark Woodbridge đģ |
-  Simon Aronsson đģ đ§ đ |
+  Simon Aronsson đģ đ§ đ đ |
 Ansem93 đ |
 Luka Peschke đģ đ |
 Zois Pagoulatos đģ đ |
@@ -123,6 +123,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Jan Kristof Nidzwetzki đ |
 lukas đģ |
 Ameya Shenoy đģ |
+  Raymon de Looff đģ |
From 6c4a5ebf273e3500ebd322759fbae4b2ca41011d Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 8 Feb 2020 13:36:52 +0000
Subject: [PATCH 240/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index a8c4609..a782e6a 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -422,6 +422,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "raymondelooff",
+ "name": "Raymon de Looff",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/9716806?v=4",
+ "profile": "https://github.com/raymondelooff",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From ecc96d6ce0d44475a79c4442237ea20bd333ff53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Paul=20G=C3=B6tzinger?=
Date: Tue, 25 Feb 2020 15:55:12 +0100
Subject: [PATCH 241/761] return on error after http.Post to gotify instance
Should fix #454
---
pkg/notifications/gotify.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/pkg/notifications/gotify.go b/pkg/notifications/gotify.go
index c9c6af8..3a4a539 100644
--- a/pkg/notifications/gotify.go
+++ b/pkg/notifications/gotify.go
@@ -83,6 +83,7 @@ func (n *gotifyTypeNotifier) Fire(entry *log.Entry) error {
resp, err := http.Post(n.getURL(), "application/json", jsonBodyBuffer)
if err != nil {
fmt.Println("Failed to send Gotify notification: ", err)
+ return
}
defer resp.Body.Close()
From 7f68dd0de4a1040cfe7c31b2c7ace1f080cf0c35 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 29 Feb 2020 19:19:59 +0000
Subject: [PATCH 242/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 6e42f6f..ef9b5ab 100644
--- a/README.md
+++ b/README.md
@@ -124,6 +124,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 lukas đģ |
 Ameya Shenoy đģ |
 Raymon de Looff đģ |
+  John Clayton đģ |
From 709239a01c13e56d437f22e5e3289884fd6e58fe Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 29 Feb 2020 19:20:00 +0000
Subject: [PATCH 243/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index a782e6a..2172b6d 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -431,6 +431,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "jsclayton",
+ "name": "John Clayton",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/704034?v=4",
+ "profile": "http://codemonkeylabs.com",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From ddd6ef564207ffbb01d8d34f665a8d4e46e9f9b1 Mon Sep 17 00:00:00 2001
From: Germs2004 <5519340+Germs2004@users.noreply.github.com>
Date: Wed, 11 Mar 2020 22:53:35 -0400
Subject: [PATCH 244/761] document the TZ environment variable
This adds a description of the "TZ" environment variable to the documentation so users will know it exists and works with Watchtower. I only learned of this variable by reading the comments on issue #160.
---
docs/arguments.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/docs/arguments.md b/docs/arguments.md
index 938dffa..19afeba 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -37,6 +37,16 @@ Environment Variable: N/A
Default: N/A
```
+## Time Zone
+Sets the time zone to be used by WatchTower's logs and the optional Cron scheduling argument (--schedule). If this environment variable is not set, Watchtower will use the default time zone: UTC.
+
+```
+ Argument: N/A
+Environment Variable: TZ
+ Type: String
+ Default: "UTC"
+```
+
## Cleanup
Removes old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
From 67c242f6f52227f758005e444447a3091fd14806 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 12 Mar 2020 18:03:25 +0000
Subject: [PATCH 245/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index ef9b5ab..679ca87 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Ameya Shenoy đģ |
 Raymon de Looff đģ |
 John Clayton đģ |
+  Germs2004 đ |
From 6941c61f8bfe487999933cb873ce87a45516b60d Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 12 Mar 2020 18:03:26 +0000
Subject: [PATCH 246/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 2172b6d..fff11ba 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -440,6 +440,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "Germs2004",
+ "name": "Germs2004",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/5519340?v=4",
+ "profile": "https://github.com/Germs2004",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
From 15d370a98332934176418ce2be8134eff9ab65d9 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Thu, 12 Mar 2020 19:06:02 +0100
Subject: [PATCH 247/761] Add "help needed" section to readme
---
README.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/README.md b/README.md
index 679ca87..b414943 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,11 @@
+> ### â ī¸ Help needed
+>
+> As I (@simskij) currently am the sole maintainer of watchtower, i'm finding it a bit hard to keep up with all issues and pull requests. Interested in helping out with triage, troubleshooting and issue handling? Let me know on gitter!
+
+
## Quick Start
With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially. Run the watchtower container with the following command:
From b051327f1bd2d096117ad458a005e26c6302e8d2 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Thu, 12 Mar 2020 19:06:30 +0100
Subject: [PATCH 248/761] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index b414943..1ee1427 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@
> ### â ī¸ Help needed
>
-> As I (@simskij) currently am the sole maintainer of watchtower, i'm finding it a bit hard to keep up with all issues and pull requests. Interested in helping out with triage, troubleshooting and issue handling? Let me know on gitter!
+> As I [@simskij](https://github.com/simskij) currently am the sole maintainer of watchtower, i'm finding it a bit hard to keep up with all issues and pull requests. Interested in helping out with triage, troubleshooting and issue handling? Let me know on gitter!
## Quick Start
From fc3decce3a04bc451b8cda8ff30863a71c5ce9f6 Mon Sep 17 00:00:00 2001
From: Lukas Willburger
Date: Fri, 13 Mar 2020 10:38:33 +0100
Subject: [PATCH 249/761] add --no-startup-message flag
---
cmd/root.go | 11 +++++++----
docs/notifications.md | 1 +
internal/flags/flags.go | 6 ++++++
3 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/cmd/root.go b/cmd/root.go
index 63a824c..ee64e56 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,7 +1,6 @@
package cmd
import (
- "github.com/containrrr/watchtower/pkg/filters"
"os"
"os/signal"
"strconv"
@@ -11,6 +10,7 @@ import (
"github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/notifications"
t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron"
@@ -123,14 +123,14 @@ func Run(c *cobra.Command, names []string) {
log.Fatal(err)
}
- if err := runUpgradesOnSchedule(filter); err != nil {
+ if err := runUpgradesOnSchedule(c, filter); err != nil {
log.Error(err)
}
os.Exit(1)
}
-func runUpgradesOnSchedule(filter t.Filter) error {
+func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter) error {
tryLockSem := make(chan bool, 1)
tryLockSem <- true
@@ -156,7 +156,10 @@ func runUpgradesOnSchedule(filter t.Filter) error {
return err
}
- log.Info("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
+ log.Info("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ }
+
cron.Start()
// Graceful shut-down on SIGINT/SIGTERM
diff --git a/docs/notifications.md b/docs/notifications.md
index b95e95e..d32e03d 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -18,6 +18,7 @@ The types of notifications to send are set by passing a comma-separated list of
## Settings
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
+- `--no-startup-message` (env. `WATCHTOWER_NOTIFICATION_NO_STARTUP_MESSAGE`): Prevents watchtower from sending a startup message.
## Available services
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 7ab09bd..c47ae2d 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -123,6 +123,12 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
+ flags.BoolP(
+ "no-startup-message",
+ "",
+ viper.GetBool("WATCHTOWER_NOTIFICATION_NO_STARTUP_MESSAGE"),
+ "Prevents watchtower from sending a startup message")
+
flags.StringP(
"notification-email-from",
"",
From 0b7069fc0c536217f0bb8dc8c5dd052820f4db1e Mon Sep 17 00:00:00 2001
From: Lukas Willburger
Date: Sun, 22 Mar 2020 17:16:44 +0100
Subject: [PATCH 250/761] Fix according to remarks
---
docs/arguments.md | 10 ++++++++++
docs/notifications.md | 1 -
internal/flags/flags.go | 12 ++++++------
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 19afeba..c172eb8 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -170,6 +170,16 @@ Environment Variable: WATCHTOWER_NO_PULL
Default: false
```
+## Without sending a startup message
+Do not send a send a message after watchtower started. Otherwise there will be an info-level notification.
+
+```
+ Argument: --no-startup-message
+Environment Variable: WATCHTOWER_NO_STARTUP_MESSAGE
+ Type: Boolean
+ Default: false
+```
+
## Run once
Run an update attempt against a container name list one time immediately and exit.
diff --git a/docs/notifications.md b/docs/notifications.md
index d32e03d..b95e95e 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -18,7 +18,6 @@ The types of notifications to send are set by passing a comma-separated list of
## Settings
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
-- `--no-startup-message` (env. `WATCHTOWER_NOTIFICATION_NO_STARTUP_MESSAGE`): Prevents watchtower from sending a startup message.
## Available services
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index c47ae2d..a60d18f 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -52,6 +52,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetBool("WATCHTOWER_NO_RESTART"),
"do not restart any containers")
+ flags.BoolP(
+ "no-startup-message",
+ "",
+ viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
+ "Prevents watchtower from sending a startup message")
+
flags.BoolP(
"cleanup",
"c",
@@ -123,12 +129,6 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
- flags.BoolP(
- "no-startup-message",
- "",
- viper.GetBool("WATCHTOWER_NOTIFICATION_NO_STARTUP_MESSAGE"),
- "Prevents watchtower from sending a startup message")
-
flags.StringP(
"notification-email-from",
"",
From d92e34e9b66613aec11c883e918f781de1fe32d7 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 22 Mar 2020 20:40:11 +0000
Subject: [PATCH 251/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 1ee1427..241d484 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Raymon de Looff đģ |
 John Clayton đģ |
 Germs2004 đ |
+  Lukas Willburger đģ |
From 2d2013e045f50a88e9f144481fd1b3cc0719585b Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 22 Mar 2020 20:40:12 +0000
Subject: [PATCH 252/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index fff11ba..a088c85 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -449,6 +449,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "lukwil",
+ "name": "Lukas Willburger",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/30203234?v=4",
+ "profile": "https://github.com/lukwil",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From be2376a4e1da4342fa9cbc96897aa2e77f0460d1 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 11:40:55 +0100
Subject: [PATCH 253/761] add shoutrrr
---
docs/notifications.md | 21 +++++++++++++++++++++
go.mod | 1 +
go.sum | 19 +++++++++++++++++++
internal/flags/flags.go | 8 +++++++-
pkg/notifications/notifier.go | 2 ++
5 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index b95e95e..6a1a9e9 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -8,6 +8,7 @@ The types of notifications to send are set by passing a comma-separated list of
- `slack` to send notifications through a Slack webhook
- `msteams` to send notifications via MSTeams webhook
- `gotify` to send notifications via Gotify
+- `shoutrrr` to send notifications via [containrrr/shoutrrr](https://github.com/containrrr/shoutrrr)
> There is currently a [bug](https://github.com/spf13/viper/issues/380) in Viper, which prevents comma-separated slices to be used when using the environment variable. A workaround is available where we instead put quotes around the environment variable value and replace the commas with spaces, as `WATCHTOWER_NOTIFICATIONS="slack msteams"`
@@ -115,3 +116,23 @@ docker run -d \
-e WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN="SuperSecretToken" \
containrrr/watchtower
```
+
+### [containrrr/shoutrrr](https://github.com/containrrr/shoutrrr)
+
+To send notifications via shoutrrr, the following command-line options, or their corresponding environment variables, can be set:
+
+- `--notification-shoutrrr-url` (env. `WATCHTOWER_NOTIFICATION_SHOUTRRR_URL`): The shoutrrr service URL to be used.
+
+Go to [https://github.com/containrrr/shoutrrr#service-urls](https://github.com/containrrr/shoutrrr#service-urls) to learn more about the different service URLs you can use.
+
+Example:
+
+```bash
+docker run -d \
+ --name watchtower \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e WATCHTOWER_NOTIFICATIONS=shoutrrr \
+ -e WATCHTOWER_NOTIFICATION_SHOUTRRR_URL=discord://channel/token \
+ -e WATCHTOWER_NOTIFICATION_SHOUTRRR_URL=slack://watchtower@token-a/token-b/token-c \
+ containrrr/watchtower
+```
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 2d2ced6..b6fad27 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
+ github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
diff --git a/go.sum b/go.sum
index 230df62..f83bd71 100644
--- a/go.sum
+++ b/go.sum
@@ -43,6 +43,8 @@ github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 h1:A7RURps5t4yDU0
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752 h1:g7iPN6gYedYNetNT7pdSO2jyZWyg9f7OqIVB4wOcEh4=
+github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -78,6 +80,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -108,6 +112,8 @@ github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLm
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -130,6 +136,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
+github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -161,6 +169,10 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -291,6 +303,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -298,6 +311,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@@ -313,6 +327,9 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
@@ -345,6 +362,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gosrc.io/xmpp v0.1.1 h1:iMtE9W3fx254+4E6rI34AOPJDqWvpfQR6EYaVMzhJ4s=
+gosrc.io/xmpp v0.1.1/go.mod h1:4JgaXzw4MnEv2sGltONtK3GMhj+h9gpQ7cO8nwbFJLU=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index a60d18f..4d54f92 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -121,7 +121,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
"notifications",
"n",
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
- " notification types to send (valid: email, slack, msteams, gotify)")
+ " notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
flags.StringP(
"notifications-level",
@@ -238,6 +238,12 @@ Should only be used for testing.
"",
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
"The Gotify Application required to query the Gotify API")
+
+ flags.StringArrayP(
+ "notification-shoutrrr-url",
+ "",
+ viper.GetStringSlice("WATCHTOWER_NOTIFICATION_SHOUTRRR_URL"),
+ "The shoutrrr URL to send notifications to")
}
// SetDefaults provides default values for environment variables
diff --git a/pkg/notifications/notifier.go b/pkg/notifications/notifier.go
index 2f25824..a23a5ee 100644
--- a/pkg/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -40,6 +40,8 @@ func NewNotifier(c *cobra.Command) *Notifier {
tn = newMsTeamsNotifier(c, acceptedLogLevels)
case gotifyType:
tn = newGotifyNotifier(c, acceptedLogLevels)
+ case shoutrrrType:
+ tn = newShoutrrrNotifier(c, acceptedLogLevels)
default:
log.Fatalf("Unknown notification type %q", t)
}
From 2b21bd46bea45a7badb71c7f23ba1d0f10f2d15c Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 13:52:04 +0100
Subject: [PATCH 254/761] add shoutrrr.go
---
pkg/notifications/shoutrrr.go | 88 +++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 pkg/notifications/shoutrrr.go
diff --git a/pkg/notifications/shoutrrr.go b/pkg/notifications/shoutrrr.go
new file mode 100644
index 0000000..4c7d2ab
--- /dev/null
+++ b/pkg/notifications/shoutrrr.go
@@ -0,0 +1,88 @@
+package notifications
+
+import (
+ "fmt"
+ "github.com/containrrr/shoutrrr"
+ t "github.com/containrrr/watchtower/pkg/types"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+const (
+ shoutrrrType = "shoutrrr"
+)
+
+// Implements Notifier, logrus.Hook
+type shoutrrrTypeNotifier struct {
+ Urls []string
+ entries []*log.Entry
+ logLevels []log.Level
+}
+
+func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
+ flags := c.PersistentFlags()
+
+ urls, _ := flags.GetStringArray("notification-shoutrrr-url")
+
+ n := &shoutrrrTypeNotifier{
+ Urls: urls,
+ logLevels: acceptedLogLevels,
+ }
+
+ log.AddHook(n)
+
+ return n
+}
+
+func (e *shoutrrrTypeNotifier) buildMessage(entries []*log.Entry) string {
+ body := ""
+ for _, entry := range entries {
+ body += entry.Time.Format("2006-01-02 15:04:05") + " (" + entry.Level.String() + "): " + entry.Message + "\r\n"
+ // We don't use fields in watchtower, so don't bother sending them.
+ }
+
+ return body
+}
+
+func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) {
+ // Do the sending in a separate goroutine so we don't block the main process.
+ msg := e.buildMessage(entries)
+ go func() {
+ for _, url := range e.Urls {
+ err := shoutrrr.Send(url, msg)
+ if err != nil {
+ // Use fmt so it doesn't trigger another notification.
+ fmt.Println("Failed to send notification via shoutrrr (url="+url+"): ", err)
+ }
+ }
+ }()
+}
+
+func (e *shoutrrrTypeNotifier) StartNotification() {
+ if e.entries == nil {
+ e.entries = make([]*log.Entry, 0, 10)
+ }
+}
+
+func (e *shoutrrrTypeNotifier) SendNotification() {
+ if e.entries == nil || len(e.entries) <= 0 {
+ return
+ }
+
+ e.sendEntries(e.entries)
+ e.entries = nil
+}
+
+func (e *shoutrrrTypeNotifier) Levels() []log.Level {
+ return e.logLevels
+}
+
+func (e *shoutrrrTypeNotifier) Fire(entry *log.Entry) error {
+ if e.entries != nil {
+ e.entries = append(e.entries, entry)
+ } else {
+ // Log output generated outside a cycle is sent immediately.
+ e.sendEntries([]*log.Entry{entry})
+ }
+ return nil
+}
From 59ce378a352f91d30f321e7a650c4ad9a4076cd5 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 13:53:30 +0100
Subject: [PATCH 255/761] Adjust flags
---
docs/notifications.md | 6 +++---
internal/flags/flags.go | 4 ++--
pkg/notifications/shoutrrr.go | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index 6a1a9e9..32ada29 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -121,7 +121,7 @@ docker run -d \
To send notifications via shoutrrr, the following command-line options, or their corresponding environment variables, can be set:
-- `--notification-shoutrrr-url` (env. `WATCHTOWER_NOTIFICATION_SHOUTRRR_URL`): The shoutrrr service URL to be used.
+- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used.
Go to [https://github.com/containrrr/shoutrrr#service-urls](https://github.com/containrrr/shoutrrr#service-urls) to learn more about the different service URLs you can use.
@@ -132,7 +132,7 @@ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
- -e WATCHTOWER_NOTIFICATION_SHOUTRRR_URL=discord://channel/token \
- -e WATCHTOWER_NOTIFICATION_SHOUTRRR_URL=slack://watchtower@token-a/token-b/token-c \
+ -e WATCHTOWER_NOTIFICATION_URL=discord://channel/token \
+ -e WATCHTOWER_NOTIFICATION_URL=slack://watchtower@token-a/token-b/token-c \
containrrr/watchtower
```
\ No newline at end of file
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 4d54f92..29c0913 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -240,9 +240,9 @@ Should only be used for testing.
"The Gotify Application required to query the Gotify API")
flags.StringArrayP(
- "notification-shoutrrr-url",
+ "notification-url",
"",
- viper.GetStringSlice("WATCHTOWER_NOTIFICATION_SHOUTRRR_URL"),
+ viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
"The shoutrrr URL to send notifications to")
}
diff --git a/pkg/notifications/shoutrrr.go b/pkg/notifications/shoutrrr.go
index 4c7d2ab..a7d786c 100644
--- a/pkg/notifications/shoutrrr.go
+++ b/pkg/notifications/shoutrrr.go
@@ -22,7 +22,7 @@ type shoutrrrTypeNotifier struct {
func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
- urls, _ := flags.GetStringArray("notification-shoutrrr-url")
+ urls, _ := flags.GetStringArray("notification-url")
n := &shoutrrrTypeNotifier{
Urls: urls,
From 5869bc52aa6be669d7d0b79bb21afb2f46bf8fff Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 14:27:09 +0100
Subject: [PATCH 256/761] Use CreateSender instead of calling Send multiple
times
---
pkg/notifications/shoutrrr.go | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/pkg/notifications/shoutrrr.go b/pkg/notifications/shoutrrr.go
index a7d786c..869ce54 100644
--- a/pkg/notifications/shoutrrr.go
+++ b/pkg/notifications/shoutrrr.go
@@ -48,11 +48,13 @@ func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) {
// Do the sending in a separate goroutine so we don't block the main process.
msg := e.buildMessage(entries)
go func() {
- for _, url := range e.Urls {
- err := shoutrrr.Send(url, msg)
+ router, _ := shoutrrr.CreateSender(e.Urls...)
+ errs := router.Send(msg, nil)
+
+ for i, err := range errs {
if err != nil {
// Use fmt so it doesn't trigger another notification.
- fmt.Println("Failed to send notification via shoutrrr (url="+url+"): ", err)
+ fmt.Println("Failed to send notification via shoutrrr (url="+e.Urls[i]+"): ", err)
}
}
}()
From b5df48279ca7e5083ab7a335d29fbf1f61a84780 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 14:34:36 +0100
Subject: [PATCH 257/761] Adjust documentation
---
docs/notifications.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index 32ada29..c2a92a1 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -124,6 +124,7 @@ To send notifications via shoutrrr, the following command-line options, or their
- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used.
Go to [https://github.com/containrrr/shoutrrr#service-urls](https://github.com/containrrr/shoutrrr#service-urls) to learn more about the different service URLs you can use.
+You can define multiple services by space separating the URLs. (See example below)
Example:
@@ -132,7 +133,6 @@ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
- -e WATCHTOWER_NOTIFICATION_URL=discord://channel/token \
- -e WATCHTOWER_NOTIFICATION_URL=slack://watchtower@token-a/token-b/token-c \
+ -e WATCHTOWER_NOTIFICATION_URL="discord://channel/token slack://watchtower@token-a/token-b/token-c" \
containrrr/watchtower
```
\ No newline at end of file
From 480f4c8ccbe34acec873cead28b60d15edec9524 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Mon, 23 Mar 2020 14:42:17 +0100
Subject: [PATCH 258/761] reuse router
---
pkg/notifications/shoutrrr.go | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/pkg/notifications/shoutrrr.go b/pkg/notifications/shoutrrr.go
index 869ce54..108e3b6 100644
--- a/pkg/notifications/shoutrrr.go
+++ b/pkg/notifications/shoutrrr.go
@@ -3,6 +3,7 @@ package notifications
import (
"fmt"
"github.com/containrrr/shoutrrr"
+ "github.com/containrrr/shoutrrr/pkg/router"
t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -15,6 +16,7 @@ const (
// Implements Notifier, logrus.Hook
type shoutrrrTypeNotifier struct {
Urls []string
+ Router *router.ServiceRouter
entries []*log.Entry
logLevels []log.Level
}
@@ -23,9 +25,11 @@ func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Noti
flags := c.PersistentFlags()
urls, _ := flags.GetStringArray("notification-url")
+ r, _ := shoutrrr.CreateSender(urls...)
n := &shoutrrrTypeNotifier{
Urls: urls,
+ Router: r,
logLevels: acceptedLogLevels,
}
@@ -48,8 +52,7 @@ func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) {
// Do the sending in a separate goroutine so we don't block the main process.
msg := e.buildMessage(entries)
go func() {
- router, _ := shoutrrr.CreateSender(e.Urls...)
- errs := router.Send(msg, nil)
+ errs := e.Router.Send(msg, nil)
for i, err := range errs {
if err != nil {
From 205b1766b4cc861a8f9a91b2699919af2ee1aeaa Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Thu, 26 Mar 2020 16:58:57 +0100
Subject: [PATCH 259/761] fix #472
---
cmd/root.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/cmd/root.go b/cmd/root.go
index ee64e56..cf2d5c6 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -113,7 +113,9 @@ func Run(c *cobra.Command, names []string) {
runOnce, _ := c.PersistentFlags().GetBool("run-once")
if runOnce {
- log.Info("Running a one time update.")
+ if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
+ log.Info("Running a one time update.")
+ }
runUpdatesWithNotifications(filter)
os.Exit(0)
return
From 82640959d49a22927761de91fc972ae82644e491 Mon Sep 17 00:00:00 2001
From: Oliver Cervera
Date: Thu, 26 Mar 2020 23:59:00 +0100
Subject: [PATCH 260/761] Start up notification (#473)
Add section regarding the ability to disable start up notification
---
docs/notifications.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/notifications.md b/docs/notifications.md
index b95e95e..bcb44ea 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -18,6 +18,7 @@ The types of notifications to send are set by passing a comma-separated list of
## Settings
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info` or `debug`.
+- Whatchtower will post a notification every time is started. This behavior [can be changed](https://containrrr.github.io/watchtower/arguments/#without_sending_a_startup_message) with an argument.
## Available services
From be673c242d529498056f2b96db96b69b9a940c65 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2020 23:59:20 +0100
Subject: [PATCH 261/761] docs: add auanasgheps as a contributor (#479)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 +++
2 files changed, 12 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index a088c85..fa348c1 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -458,6 +458,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "auanasgheps",
+ "name": "Oliver Cervera",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/20586878?v=4",
+ "profile": "https://github.com/auanasgheps",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 241d484..b055aba 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Germs2004 đ |
 Lukas Willburger đģ |
+
+  Oliver Cervera đ |
+
From 5a9459c1669b0aebdc7078a7b2f90cd40fb0c2ff Mon Sep 17 00:00:00 2001
From: Oliver Cervera
Date: Fri, 27 Mar 2020 00:00:10 +0100
Subject: [PATCH 262/761] Time Zone improvements (#471)
Clarify where to find values for Time Zones
---
docs/arguments.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/arguments.md b/docs/arguments.md
index c172eb8..7e55dc4 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -39,6 +39,7 @@ Environment Variable: N/A
## Time Zone
Sets the time zone to be used by WatchTower's logs and the optional Cron scheduling argument (--schedule). If this environment variable is not set, Watchtower will use the default time zone: UTC.
+To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_.
```
Argument: N/A
From 5ed39025a10981129e41409bbed8328e487147e7 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2020 23:05:29 +0000
Subject: [PATCH 263/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index b055aba..9d61fd8 100644
--- a/README.md
+++ b/README.md
@@ -135,6 +135,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Oliver Cervera đ |
+  Victor Moura â ī¸ |
From ffcf7af977cc6a21d377c9de1360de6164376128 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2020 23:05:30 +0000
Subject: [PATCH 264/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index fa348c1..bc1ad9b 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -467,6 +467,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "victorcmoura",
+ "name": "Victor Moura",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/26290053?v=4",
+ "profile": "https://github.com/victorcmoura",
+ "contributions": [
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
From 565736322056ab2bfd78a1722d661ae516370a85 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2020 23:08:21 +0000
Subject: [PATCH 265/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 9d61fd8..396a155 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Oliver Cervera đ |
 Victor Moura â ī¸ |
+  Maximilian Brandau đģ |
From fc8636d4d4cc74caecf706217d9d797fdb0ba70f Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2020 23:08:22 +0000
Subject: [PATCH 266/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index bc1ad9b..4c48400 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -476,6 +476,15 @@
"contributions": [
"test"
]
+ },
+ {
+ "login": "mbrandau",
+ "name": "Maximilian Brandau",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/12972798?v=4",
+ "profile": "https://github.com/mbrandau",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
From 0462c30bfb0c596a75b4f92ad4a61d89ba5c013b Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 27 Mar 2020 11:56:58 +0100
Subject: [PATCH 267/761] clarify container selection
---
docs/container-selection.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/container-selection.md b/docs/container-selection.md
index 4c3312c..459d53b 100644
--- a/docs/container-selection.md
+++ b/docs/container-selection.md
@@ -12,7 +12,7 @@ Or, it can be specified as part of the `docker run` command line:
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
```
-If you need to include only some containers, pass the `--label-enable` flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
+If you need to [include only containers with the enable label](https://containrrr.github.io/watchtower/arguments/#filter_by_enable_label), pass the `--label-enable` flag or the `WATCTOWER_LABEL_ENV` environment variable on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
@@ -22,4 +22,4 @@ Or, it can be specified as part of the `docker run` command line:
```bash
docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
-```
\ No newline at end of file
+```
From 7542a247d8521be7993c6819252f1c2525ac66d6 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 27 Mar 2020 11:57:14 +0100
Subject: [PATCH 268/761] Update container-selection.md
---
docs/container-selection.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/container-selection.md b/docs/container-selection.md
index 459d53b..7283ce9 100644
--- a/docs/container-selection.md
+++ b/docs/container-selection.md
@@ -12,7 +12,7 @@ Or, it can be specified as part of the `docker run` command line:
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
```
-If you need to [include only containers with the enable label](https://containrrr.github.io/watchtower/arguments/#filter_by_enable_label), pass the `--label-enable` flag or the `WATCTOWER_LABEL_ENV` environment variable on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
+If you need to [include only containers with the enable label](https://containrrr.github.io/watchtower/arguments/#filter_by_enable_label), pass the `--label-enable` flag or the `WATCTOWER_LABEL_ENABLE` environment variable on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
From 638c697dece24205c45e76afc1204935f3dc710c Mon Sep 17 00:00:00 2001
From: sixcorners
Date: Fri, 27 Mar 2020 07:57:04 -0500
Subject: [PATCH 269/761] Copy note about setting both interval and schedule
---
docs/arguments.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 7e55dc4..e55bd3b 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -119,7 +119,7 @@ Environment Variable: WATCHTOWER_REVIVE_STOPPED
```
## Poll interval
-Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
+Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Either `--schedule` or a poll interval can be defined, but not both.
```
Argument: --interval, -i
@@ -192,7 +192,8 @@ Environment Variable: WATCHTOWER_RUN_ONCE
```
## Scheduling
-[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
+[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
+can be defined, but not both. An example: `--schedule "0 0 4 * * *"`
```
Argument: --schedule, -s
From ce459635a536dd504e39bd538f0af209a9d2f8f9 Mon Sep 17 00:00:00 2001
From: Andrew
Date: Fri, 27 Mar 2020 14:58:57 -0500
Subject: [PATCH 270/761] Update arguments.md
Add clarity to --monitor-only, information on alternative timezone setting method.
---
docs/arguments.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 7e55dc4..df2d09a 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -39,7 +39,7 @@ Environment Variable: N/A
## Time Zone
Sets the time zone to be used by WatchTower's logs and the optional Cron scheduling argument (--schedule). If this environment variable is not set, Watchtower will use the default time zone: UTC.
-To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_.
+To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_. The timezome can alternatively be set by volume mounting your hosts /etc/timezone file. `-v /etc/timezone:/etc/timezone:ro`
```
Argument: N/A
@@ -139,7 +139,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
```
## Without updating containers
-Will only monitor for new images, not update the containers.
+Will only monitor for new images, not update the containers. Please note: Due to Docker API limitations the latest image will still be pulled from the registry.
```
Argument: --monitor-only
From 52a8bd3bd26afef72073c5b09da567a18a7df0b8 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 28 Mar 2020 18:49:23 +0100
Subject: [PATCH 271/761] Update arguments.md
---
docs/arguments.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index df2d09a..c90c9ea 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -139,7 +139,11 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
```
## Without updating containers
-Will only monitor for new images, not update the containers. Please note: Due to Docker API limitations the latest image will still be pulled from the registry.
+Will only monitor for new images, not update the containers.
+
+> ### â ī¸ Please note
+>
+> Due to Docker API limitations the latest image will still be pulled from the registry.
```
Argument: --monitor-only
From d001fbc48447bedbb328c4d3bd10969dc28abea6 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Mar 2020 17:49:57 +0000
Subject: [PATCH 272/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 396a155..729630c 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Oliver Cervera đ |
 Victor Moura â ī¸ |
 Maximilian Brandau đģ |
+  Andrew đ |
From 0c35e46c1b1ca54a481b3c8d6b84001b7e86b8c0 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Mar 2020 17:49:58 +0000
Subject: [PATCH 273/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 4c48400..7823e3d 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -485,6 +485,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "aneisch",
+ "name": "Andrew",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/6991461?v=4",
+ "profile": "https://github.com/aneisch",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
From 1d1c630f7a79c59c0383a12649dc336389886d6e Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 25 Nov 2019 21:11:12 +0100
Subject: [PATCH 274/761] feat: add timeout override for pre-update lifecycle
hook
---
docs/lifecycle-hooks.md | 13 ++++++++++
go.sum | 2 ++
internal/actions/update.go | 3 +--
internal/flags/flags.go | 6 +++--
pkg/container/client.go | 50 +++++++++++++++++++++++++++++---------
pkg/container/container.go | 22 ++++++++++++++++-
pkg/container/metadata.go | 17 +++++++------
7 files changed, 89 insertions(+), 24 deletions(-)
diff --git a/docs/lifecycle-hooks.md b/docs/lifecycle-hooks.md
index 071726c..f8bc640 100644
--- a/docs/lifecycle-hooks.md
+++ b/docs/lifecycle-hooks.md
@@ -46,6 +46,19 @@ docker run -d \
--label=com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh" \
```
+### Timeouts
+The timeout for all lifecycle commands is 60 seconds. After that, a timeout will
+occur, forcing Watchtower to continue the update loop.
+
+#### Pre-update timeouts
+
+For the `pre-update` lifecycle command, it is possible to override this timeout to
+allow the script to finish before forcefully killing it. This is done by adding the
+label `com.centurylinklabs.watchtower.lifecycle.pre-update-timeout` followed by
+the timeout expressed in minutes.
+
+If the label value is explicitly set to `0`, the timeout will be disabled.
+
### Execution failure
The failure of a command to execute, identified by an exit code different than
diff --git a/go.sum b/go.sum
index 230df62..9531de6 100644
--- a/go.sum
+++ b/go.sum
@@ -61,6 +61,7 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4 h1:34LfsqlE2kEvmGP9qbRoPvOWkmluYGzmlvWVTzwvT0A=
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k=
@@ -284,6 +285,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 874e705..31ceafe 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -75,7 +75,6 @@ func stopStaleContainer(container container.Container, client container.Client,
}
if params.LifecycleHooks {
lifecycle.ExecutePreUpdateCommand(client, container)
-
}
if err := client.StopContainer(container, params.Timeout); err != nil {
@@ -140,4 +139,4 @@ func checkDependencies(containers []container.Container) {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index a60d18f..039b574 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -30,12 +30,14 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
"poll interval (in seconds)")
- flags.StringP("schedule",
+ flags.StringP(
+ "schedule",
"s",
viper.GetString("WATCHTOWER_SCHEDULE"),
"the cron expression which defines when to update")
- flags.DurationP("stop-timeout",
+ flags.DurationP(
+ "stop-timeout",
"t",
viper.GetDuration("WATCHTOWER_TIMEOUT"),
"timeout before a container is forcefully stopped")
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 607b84c..a74bfba 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -29,9 +29,8 @@ type Client interface {
StartContainer(Container) (string, error)
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
- ExecuteCommand(containerID string, command string) error
+ ExecuteCommand(containerID string, command string, timeout int) error
RemoveImageByID(string) error
-
}
// NewClient returns a new Client instance which can be used to interact with
@@ -301,7 +300,7 @@ func (client dockerClient) RemoveImageByID(id string) error {
return err
}
-func (client dockerClient) ExecuteCommand(containerID string, command string) error {
+func (client dockerClient) ExecuteCommand(containerID string, command string, timeout int) error {
bg := context.Background()
// Create the exec
@@ -331,7 +330,7 @@ func (client dockerClient) ExecuteCommand(containerID string, command string) er
return err
}
- var execOutput string
+ var output string
if attachErr == nil {
defer response.Close()
var writer bytes.Buffer
@@ -339,26 +338,56 @@ func (client dockerClient) ExecuteCommand(containerID string, command string) er
if err != nil {
log.Error(err)
} else if written > 0 {
- execOutput = strings.TrimSpace(writer.String())
+ output = strings.TrimSpace(writer.String())
}
}
// Inspect the exec to get the exit code and print a message if the
// exit code is not success.
- execInspect, err := client.api.ContainerExecInspect(bg, exec.ID)
+ err = client.waitForExecOrTimeout(bg, exec.ID, output, timeout)
if err != nil {
return err
}
- if execInspect.ExitCode > 0 {
- log.Errorf("Command exited with code %v.", execInspect.ExitCode)
- log.Error(execOutput)
+ return nil
+}
+
+func (client dockerClient) waitForExecOrTimeout(bg context.Context, ID string, execOutput string, timeout int) error {
+ var ctx context.Context
+ var cancel context.CancelFunc
+
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(bg, time.Duration(timeout)*time.Minute)
+ defer cancel()
} else {
+ ctx = bg
+ }
+
+ for {
+ execInspect, err := client.api.ContainerExecInspect(ctx, ID)
+
+ log.WithFields(log.Fields{
+ "exit-code": execInspect.ExitCode,
+ "exec-id": execInspect.ExecID,
+ "running": execInspect.Running,
+ }).Debug("Awaiting timeout or completion")
+
+ if err != nil {
+ return err
+ }
+ if execInspect.Running == true {
+ time.Sleep(1 * time.Second)
+ continue
+ }
if len(execOutput) > 0 {
log.Infof("Command output:\n%v", execOutput)
}
+ if execInspect.ExitCode > 0 {
+ log.Errorf("Command exited with code %v.", execInspect.ExitCode)
+ log.Error(execOutput)
+ }
+ break
}
-
return nil
}
@@ -377,7 +406,6 @@ func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Durat
return nil
}
}
-
time.Sleep(1 * time.Second)
}
}
diff --git a/pkg/container/container.go b/pkg/container/container.go
index f88ff91..32e5a31 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -2,10 +2,11 @@ package container
import (
"fmt"
- "github.com/containrrr/watchtower/internal/util"
"strconv"
"strings"
+ "github.com/containrrr/watchtower/internal/util"
+
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
)
@@ -118,6 +119,25 @@ func (c Container) IsWatchtower() bool {
return ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
}
+// PreUpdateTimeout checks whether a container has a specific timeout set
+// for how long the pre-update command is allowed to run. This value is expressed
+// either as an integer, in minutes, or as "off" which will allow the command/script
+// to run indefinitely. Users should be cautious with the off option, as that
+// could result in watchtower waiting forever.
+func (c Container) PreUpdateTimeout() int {
+ var minutes int
+ var err error
+
+ val := c.getLabelValueOrEmpty(preUpdateTimeoutLabel)
+
+ minutes, err = strconv.Atoi(val)
+ if err != nil || val == "" {
+ return 1
+ }
+
+ return minutes
+}
+
// StopSignal returns the custom stop signal (if any) that is encoded in the
// container's metadata. If the container has not specified a custom stop
// signal, the empty string "" is returned.
diff --git a/pkg/container/metadata.go b/pkg/container/metadata.go
index 0e04350..fe5a055 100644
--- a/pkg/container/metadata.go
+++ b/pkg/container/metadata.go
@@ -1,14 +1,15 @@
package container
const (
- watchtowerLabel = "com.centurylinklabs.watchtower"
- signalLabel = "com.centurylinklabs.watchtower.stop-signal"
- enableLabel = "com.centurylinklabs.watchtower.enable"
- zodiacLabel = "com.centurylinklabs.zodiac.original-image"
- preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
- postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
- preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
- postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
+ watchtowerLabel = "com.centurylinklabs.watchtower"
+ signalLabel = "com.centurylinklabs.watchtower.stop-signal"
+ enableLabel = "com.centurylinklabs.watchtower.enable"
+ zodiacLabel = "com.centurylinklabs.zodiac.original-image"
+ preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
+ postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
+ preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
+ postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
+ preUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout"
)
// GetLifecyclePreCheckCommand returns the pre-check command set in the container metadata or an empty string
From 1d3ffc728daa522d42cb09a0d9ce3ae3e4367a66 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Mon, 25 Nov 2019 21:19:38 +0100
Subject: [PATCH 275/761] fix: update mock client for tests
---
internal/actions/actions_suite_test.go | 59 ++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index 2c9b0c8..b633b6a 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -132,3 +132,62 @@ var _ = Describe("the actions package", func() {
})
})
+func createMockContainer(id string, name string, image string, created time.Time) container.Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: id,
+ Image: image,
+ Name: name,
+ Created: created.String(),
+ },
+ }
+ return *container.NewContainer(&content, nil)
+}
+
+type mockClient struct {
+ TestData *TestData
+ api cli.CommonAPIClient
+ pullImages bool
+ removeVolumes bool
+}
+
+type TestData struct {
+ TriedToRemoveImage bool
+ NameOfContainerToKeep string
+ Containers []container.Container
+}
+
+func (client mockClient) ListContainers(f t.Filter) ([]container.Container, error) {
+ return client.TestData.Containers, nil
+}
+
+func (client mockClient) StopContainer(c container.Container, d time.Duration) error {
+ if c.Name() == client.TestData.NameOfContainerToKeep {
+ return errors.New("tried to stop the instance we want to keep")
+ }
+ return nil
+}
+func (client mockClient) StartContainer(c container.Container) (string, error) {
+ panic("Not implemented")
+}
+
+func (client mockClient) RenameContainer(c container.Container, s string) error {
+ panic("Not implemented")
+}
+
+func (client mockClient) RemoveImage(c container.Container) error {
+ client.TestData.TriedToRemoveImage = true
+ return nil
+}
+
+func (client mockClient) GetContainer(containerID string) (container.Container, error) {
+ return container.Container{}, nil
+}
+
+func (client mockClient) ExecuteCommand(containerID string, command string, timeout int) error {
+ return nil
+}
+
+func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
+ panic("Not implemented")
+}
From fac26dfc721e51cce7a49e6e32e3bd07ba91d07a Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 27 Dec 2019 12:05:56 +0100
Subject: [PATCH 276/761] fix: improve logging
---
pkg/notifications/notifier.go | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/pkg/notifications/notifier.go b/pkg/notifications/notifier.go
index 2f25824..20e8337 100644
--- a/pkg/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -27,8 +27,10 @@ func NewNotifier(c *cobra.Command) *Notifier {
acceptedLogLevels := slackrus.LevelThreshold(logLevel)
// Parse types and create notifiers.
- types, _ := f.GetStringSlice("notifications")
-
+ types, err := f.GetStringSlice("notifications")
+ if err != nil {
+ log.WithField("could not read notifications argument", log.Fields{ "Error": err }).Fatal()
+ }
for _, t := range types {
var tn ty.Notifier
switch t {
From c1a0da9a9d5eb6587c3a5b0020b6c7113e18fade Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 3 Jan 2020 18:51:32 +0100
Subject: [PATCH 277/761] feature/367 fix: skip container if pre-update command
fails
---
internal/actions/update.go | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 31ceafe..1694c59 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -74,9 +74,13 @@ func stopStaleContainer(container container.Container, client container.Client,
return
}
if params.LifecycleHooks {
- lifecycle.ExecutePreUpdateCommand(client, container)
+ if err := lifecycle.ExecutePreUpdateCommand(client, container); err != nil {
+ log.Error(err)
+ log.Info("Skipping container as the pre-update command failed")
+ return
+ }
}
-
+
if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
@@ -139,4 +143,4 @@ func checkDependencies(containers []container.Container) {
}
}
}
-}
\ No newline at end of file
+}
From 98c60d744143b9dc245ddfdc14270b22cb739b73 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 28 Mar 2020 19:48:04 +0100
Subject: [PATCH 278/761] fix some errors and clean up
---
internal/actions/actions_suite_test.go | 51 +-------------------------
internal/actions/mocks/client.go | 2 +-
pkg/container/container.go | 4 +-
pkg/lifecycle/lifecycle.go | 16 ++++----
4 files changed, 12 insertions(+), 61 deletions(-)
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index b633b6a..5bfbcdd 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -9,6 +9,7 @@ import (
"github.com/containrrr/watchtower/pkg/container/mocks"
cli "github.com/docker/docker/client"
+ "github.com/docker/docker/api/types"
. "github.com/containrrr/watchtower/internal/actions/mocks"
. "github.com/onsi/ginkgo"
@@ -142,52 +143,4 @@ func createMockContainer(id string, name string, image string, created time.Time
},
}
return *container.NewContainer(&content, nil)
-}
-
-type mockClient struct {
- TestData *TestData
- api cli.CommonAPIClient
- pullImages bool
- removeVolumes bool
-}
-
-type TestData struct {
- TriedToRemoveImage bool
- NameOfContainerToKeep string
- Containers []container.Container
-}
-
-func (client mockClient) ListContainers(f t.Filter) ([]container.Container, error) {
- return client.TestData.Containers, nil
-}
-
-func (client mockClient) StopContainer(c container.Container, d time.Duration) error {
- if c.Name() == client.TestData.NameOfContainerToKeep {
- return errors.New("tried to stop the instance we want to keep")
- }
- return nil
-}
-func (client mockClient) StartContainer(c container.Container) (string, error) {
- panic("Not implemented")
-}
-
-func (client mockClient) RenameContainer(c container.Container, s string) error {
- panic("Not implemented")
-}
-
-func (client mockClient) RemoveImage(c container.Container) error {
- client.TestData.TriedToRemoveImage = true
- return nil
-}
-
-func (client mockClient) GetContainer(containerID string) (container.Container, error) {
- return container.Container{}, nil
-}
-
-func (client mockClient) ExecuteCommand(containerID string, command string, timeout int) error {
- return nil
-}
-
-func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
- panic("Not implemented")
-}
+}
\ No newline at end of file
diff --git a/internal/actions/mocks/client.go b/internal/actions/mocks/client.go
index dad2506..2145484 100644
--- a/internal/actions/mocks/client.go
+++ b/internal/actions/mocks/client.go
@@ -73,7 +73,7 @@ func (client MockClient) GetContainer(containerID string) (container.Container,
}
// ExecuteCommand is a mock method
-func (client MockClient) ExecuteCommand(containerID string, command string) error {
+func (client MockClient) ExecuteCommand(containerID string, command string, timeout int) error {
return nil
}
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 32e5a31..fb495fe 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -121,8 +121,8 @@ func (c Container) IsWatchtower() bool {
// PreUpdateTimeout checks whether a container has a specific timeout set
// for how long the pre-update command is allowed to run. This value is expressed
-// either as an integer, in minutes, or as "off" which will allow the command/script
-// to run indefinitely. Users should be cautious with the off option, as that
+// either as an integer, in minutes, or as 0 which will allow the command/script
+// to run indefinitely. Users should be cautious with the 0 option, as that
// could result in watchtower waiting forever.
func (c Container) PreUpdateTimeout() int {
var minutes int
diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go
index 9823f9d..9311355 100644
--- a/pkg/lifecycle/lifecycle.go
+++ b/pkg/lifecycle/lifecycle.go
@@ -37,7 +37,7 @@ func ExecutePreCheckCommand(client container.Client, container container.Contain
}
log.Info("Executing pre-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ if err := client.ExecuteCommand(container.ID(), command, 1); err != nil {
log.Error(err)
}
}
@@ -51,24 +51,22 @@ func ExecutePostCheckCommand(client container.Client, container container.Contai
}
log.Info("Executing post-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ if err := client.ExecuteCommand(container.ID(), command, 1); err != nil {
log.Error(err)
}
}
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
-func ExecutePreUpdateCommand(client container.Client, container container.Container) {
-
+func ExecutePreUpdateCommand(client container.Client, container container.Container) error {
+ timeout := container.PreUpdateTimeout()
command := container.GetLifecyclePreUpdateCommand()
if len(command) == 0 {
log.Debug("No pre-update command supplied. Skipping")
- return
+ return nil
}
log.Info("Executing pre-update command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
+ return client.ExecuteCommand(container.ID(), command, timeout)
}
// ExecutePostUpdateCommand tries to run the post-update lifecycle hook for a single container.
@@ -86,7 +84,7 @@ func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
}
log.Info("Executing post-update command.")
- if err := client.ExecuteCommand(newContainerID, command); err != nil {
+ if err := client.ExecuteCommand(newContainerID, command, 1); err != nil {
log.Error(err)
}
}
From 0e131e38bd39b44e74b2416ae29239e55ae9fcc2 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Mar 2020 18:52:25 +0000
Subject: [PATCH 279/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 729630c..a5025e3 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Victor Moura â ī¸ |
 Maximilian Brandau đģ |
 Andrew đ |
+  sixcorners đ |
From 6590ef248f9dd9880a7800a13c2dcddbe06fe22a Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 28 Mar 2020 18:52:26 +0000
Subject: [PATCH 280/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 7823e3d..f438596 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -494,6 +494,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "sixcorners",
+ "name": "sixcorners",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/585501?v=4",
+ "profile": "https://github.com/sixcorners",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
From 90ef4022f7c25386c53ce7e687aeb19411669039 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 28 Mar 2020 20:05:59 +0100
Subject: [PATCH 281/761] add additional note on gcloud credentials
---
docs/private-registries.md | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/docs/private-registries.md b/docs/private-registries.md
index 13e7618..3606c2a 100644
--- a/docs/private-registries.md
+++ b/docs/private-registries.md
@@ -1,12 +1,17 @@
-Watchtower supports private Docker image registries. In many cases, accessing a private registry requires a valid username and password (i.e., _credentials_). In order to operate in such an environment, watchtower needs to know the credentials to access the registry.
+Watchtower supports private Docker image registries. In many cases, accessing a private registry
+requires a valid username and password (i.e., _credentials_). In order to operate in such an
+environment, watchtower needs to know the credentials to access the registry.
-The credentials can be provided to watchtower in a configuration file called `config.json`. There are two ways to generate this configuration file:
+The credentials can be provided to watchtower in a configuration file called `config.json`.
+There are two ways to generate this configuration file:
* The configuration file can be created manually.
* Call `docker login ` and share the resulting configuration file.
### Create the configuration file manually
-Create a new configuration file with the following syntax and a base64 encoded username and password `auth` string:
+Create a new configuration file with the following syntax and a base64 encoded username and
+password `auth` string:
+
```json
{
"auths": {
@@ -17,27 +22,40 @@ Create a new configuration file with the following syntax and a base64 encoded u
}
```
-`` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
+`` needs to be replaced by the name of your private registry
+(e.g., `my-private-registry.example.org`)
The required `auth` string can be generated as follows:
```bash
echo -n 'username:password' | base64
```
-When the watchtower Docker container is started, the created configuration file (`/config.json` in this example) needs to be passed to the container:
+> ### âšī¸ Username and Password for GCloud
+>
+> For gcloud, we'll use `__json_key` as our username and the content
+> of `gcloudauth.json` as the password.
+
+When the watchtower Docker container is started, the created configuration file
+(`/config.json` in this example) needs to be passed to the container:
+
```bash
docker run [...] -v /config.json:/config.json containrrr/watchtower
```
### Share the Docker configuration file
-To pull an image from a private registry, `docker login` needs to be called first, to get access to the registry. The provided credentials are stored in a configuration file called `/.docker/config.json`. This configuration file can be directly used by watchtower. In this case, the creation of an additional configuration file is not necessary.
+To pull an image from a private registry, `docker login` needs to be called first, to get access
+to the registry. The provided credentials are stored in a configuration file called `/.docker/config.json`.
+This configuration file can be directly used by watchtower. In this case, the creation of an
+additional configuration file is not necessary.
When the Docker container is started, pass the configuration file to watchtower:
+
```bash
docker run [...] -v /.docker/config.json:/config.json containrrr/watchtower
```
When creating the watchtower container via docker-compose, use the following lines:
+
```yaml
version: "3"
[...]
From 21e8799ce3c6276490aee778bf129ceaf73e46e9 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Tue, 31 Mar 2020 11:29:49 +0200
Subject: [PATCH 282/761] Update documentation
---
docs/notifications.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/notifications.md b/docs/notifications.md
index c2a92a1..3698866 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -123,7 +123,7 @@ To send notifications via shoutrrr, the following command-line options, or their
- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used.
-Go to [https://github.com/containrrr/shoutrrr#service-urls](https://github.com/containrrr/shoutrrr#service-urls) to learn more about the different service URLs you can use.
+Go to [containrrr.github.io/shoutrrr/services/overview](https://containrrr.github.io/shoutrrr/services/overview) to learn more about the different service URLs you can use.
You can define multiple services by space separating the URLs. (See example below)
Example:
@@ -133,6 +133,6 @@ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
- -e WATCHTOWER_NOTIFICATION_URL="discord://channel/token slack://watchtower@token-a/token-b/token-c" \
+ -e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
containrrr/watchtower
```
\ No newline at end of file
From 2381c279f40afcd34a843300748d5ffcdbdb7c24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?=
Date: Wed, 1 Apr 2020 19:38:35 +0200
Subject: [PATCH 283/761] docs: update cron docs link
Update the robfig/cron documentation link which currently points to the v3 version and not the v1 version that watchtower uses
---
docs/arguments.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 6aabb85..3a5fab5 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -196,7 +196,7 @@ Environment Variable: WATCHTOWER_RUN_ONCE
```
## Scheduling
-[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
+[Cron expression](https://pkg.go.dev/github.com/robfig/cron@v1.2.0?tab=doc#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
can be defined, but not both. An example: `--schedule "0 0 4 * * *"`
```
From c49cc506c11ebe149ed0a9c247c0e69f66dfe588 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2020 19:26:53 +0000
Subject: [PATCH 284/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index a5025e3..ed78058 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Maximilian Brandau đģ |
 Andrew đ |
 sixcorners đ |
+  nils mÃĨsÊn đ |
From 8cbc9ebafb5baa8c5ea7132dbd916c52c01e0ff2 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2020 19:26:54 +0000
Subject: [PATCH 285/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f438596..7965cc4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -503,6 +503,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "piksel",
+ "name": "nils mÃĨsÊn",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/807383?v=4",
+ "profile": "https://piksel.se",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
From a8d453b4c7297d0bf5de58a2a0386945445a08a2 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Sun, 5 Apr 2020 11:10:47 +0200
Subject: [PATCH 286/761] update shoutrrr
---
go.mod | 2 +-
go.sum | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index b6fad27..9d0138d 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
- github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752
+ github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
diff --git a/go.sum b/go.sum
index f83bd71..a9aff4f 100644
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,8 @@ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882b
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752 h1:g7iPN6gYedYNetNT7pdSO2jyZWyg9f7OqIVB4wOcEh4=
github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
+github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13 h1:5KIwcRac24xehTL/xrhXNIiI9JnV2Mbfl52OgGbloIM=
+github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
From d17e2887fba066916b0dce7ddfa01eab374cc831 Mon Sep 17 00:00:00 2001
From: Maximilian Brandau
Date: Sun, 5 Apr 2020 11:51:44 +0200
Subject: [PATCH 287/761] remove old shoutrrr version
---
go.sum | 2 --
1 file changed, 2 deletions(-)
diff --git a/go.sum b/go.sum
index a9aff4f..97a634b 100644
--- a/go.sum
+++ b/go.sum
@@ -43,8 +43,6 @@ github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 h1:A7RURps5t4yDU0
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752 h1:g7iPN6gYedYNetNT7pdSO2jyZWyg9f7OqIVB4wOcEh4=
-github.com/containrrr/shoutrrr v0.0.0-20200308125025-1981b9ef7752/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13 h1:5KIwcRac24xehTL/xrhXNIiI9JnV2Mbfl52OgGbloIM=
github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
From 1d296a3e1d22cfe3abb89785ec90e3114b894151 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 5 Apr 2020 15:47:34 +0000
Subject: [PATCH 288/761] docs: update README.md [skip ci]
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index ed78058..29f5dab 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Andrew đ |
 sixcorners đ |
 nils mÃĨsÊn đ |
+  Arne Jørgensen â ī¸ đ |
From 928da2ba546002264b71e7cfae2f56f4ba90dfe7 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 5 Apr 2020 15:47:35 +0000
Subject: [PATCH 289/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 7965cc4..da468d1 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -512,6 +512,16 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "arnested",
+ "name": "Arne Jørgensen",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/190005?v=4",
+ "profile": "https://arnested.dk",
+ "contributions": [
+ "test",
+ "review"
+ ]
}
],
"contributorsPerLine": 7,
From 3831f5c3ecce95b1a697c71b7acf805770dd61dc Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 5 Apr 2020 15:48:36 +0000
Subject: [PATCH 290/761] docs: update README.md [skip ci]
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 396a155..e7127ce 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Oliver Cervera đ |
 Victor Moura â ī¸ |
-  Maximilian Brandau đģ |
+  Maximilian Brandau đģ â ī¸ |
From fbd272342cf7fdda4e38adc39f2aa576d4c01078 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sun, 5 Apr 2020 15:48:37 +0000
Subject: [PATCH 291/761] docs: update .all-contributorsrc [skip ci]
---
.all-contributorsrc | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 4c48400..8147f89 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -483,7 +483,8 @@
"avatar_url": "https://avatars3.githubusercontent.com/u/12972798?v=4",
"profile": "https://github.com/mbrandau",
"contributions": [
- "code"
+ "code",
+ "test"
]
}
],
From a86ec363f56b8a1d4c43bf89eb723530f21e2ffe Mon Sep 17 00:00:00 2001
From: PatSki123
Date: Tue, 14 Apr 2020 09:18:42 -0400
Subject: [PATCH 292/761] Typo Correction (#503)
Fixed typo under "Without sending a startup message" section.
---
docs/arguments.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/arguments.md b/docs/arguments.md
index 3a5fab5..29ac8ca 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -176,7 +176,7 @@ Environment Variable: WATCHTOWER_NO_PULL
```
## Without sending a startup message
-Do not send a send a message after watchtower started. Otherwise there will be an info-level notification.
+Do not send a message after watchtower started. Otherwise there will be an info-level notification.
```
Argument: --no-startup-message
From 557f4abcb40c1ef2c99c85359932043c0afa3696 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Tue, 14 Apr 2020 15:42:30 +0200
Subject: [PATCH 293/761] docs: add patski123 as a contributor (#505)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 +++
2 files changed, 12 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 41f5863..9ade4f4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -523,6 +523,15 @@
"test",
"review"
]
+ },
+ {
+ "login": "patski123",
+ "name": "PatSki123",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/19295295?v=4",
+ "profile": "https://github.com/patski123",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 33773bb..b459e1b 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 nils mÃĨsÊn đ |
 Arne Jørgensen â ī¸ đ |
+
+  PatSki123 đ |
+
From 0217e116c47378f3eea88523af91084867c1950e Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Mon, 20 Apr 2020 11:17:14 -0300
Subject: [PATCH 294/761] Watchtower HTTP API based updates (#432)
* Added HTTP API trigger to update running images
* Adds HTTP API authentication token parameter and handling
* Exposes port 8080 in Dockerfile to allow inter-container update triggering via HTTP API
* Fixes codacy issue
* Adds API usage doc
* Fix grammar
* Moves api logic to a package of its own
* Makes WT exit if token has not been set in HTTP API mode
* Adds lock to prevent concurrent updates when in HTTP API mode
Co-authored-by: Simon Aronsson
---
cmd/root.go | 13 +++++++++
dockerfiles/Dockerfile | 2 ++
docs/arguments.md | 20 ++++++++++++++
docs/http-api-mode.md | 35 +++++++++++++++++++++++
internal/flags/flags.go | 12 ++++++++
pkg/api/api.go | 61 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 143 insertions(+)
create mode 100644 docs/http-api-mode.md
create mode 100644 pkg/api/api.go
diff --git a/cmd/root.go b/cmd/root.go
index cf2d5c6..2a87c1d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -9,6 +9,7 @@ import (
"github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/internal/flags"
+ "github.com/containrrr/watchtower/pkg/api"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/notifications"
@@ -111,6 +112,18 @@ func PreRun(cmd *cobra.Command, args []string) {
func Run(c *cobra.Command, names []string) {
filter := filters.BuildFilter(names, enableLabel)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
+ httpAPI, _ := c.PersistentFlags().GetBool("http-api")
+
+ if httpAPI {
+ apiToken, _ := c.PersistentFlags().GetString("http-api-token")
+
+ if err := api.SetupHTTPUpdates(apiToken, func() { runUpdatesWithNotifications(filter) }); err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+
+ api.WaitForHTTPUpdates()
+ }
if runOnce {
if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile
index f792d32..7e28eb2 100644
--- a/dockerfiles/Dockerfile
+++ b/dockerfiles/Dockerfile
@@ -14,5 +14,7 @@ COPY --from=alpine \
/usr/share/zoneinfo \
/usr/share/zoneinfo
+EXPOSE 8080
+
COPY watchtower /
ENTRYPOINT ["/watchtower"]
diff --git a/docs/arguments.md b/docs/arguments.md
index 29ac8ca..1100725 100644
--- a/docs/arguments.md
+++ b/docs/arguments.md
@@ -193,6 +193,26 @@ Run an update attempt against a container name list one time immediately and exi
Environment Variable: WATCHTOWER_RUN_ONCE
Type: Boolean
Default: false
+```
+
+## HTTP API Mode
+Runs Watchtower in HTTP API mode, only allowing image updates to be triggered by an HTTP request.
+
+```
+ Argument: --http-api
+Environment Variable: WATCHTOWER_HTTP_API
+ Type: Boolean
+ Default: false
+```
+
+## HTTP API Token
+Sets an authentication token to HTTP API requests.
+
+```
+ Argument: --http-api-token
+Environment Variable: WATCHTOWER_HTTP_API_TOKEN
+ Type: String
+ Default: -
```
## Scheduling
diff --git a/docs/http-api-mode.md b/docs/http-api-mode.md
new file mode 100644
index 0000000..7d14d09
--- /dev/null
+++ b/docs/http-api-mode.md
@@ -0,0 +1,35 @@
+Watchtower provides an HTTP API mode that enables an HTTP endpoint that can be requested to trigger container updating. The current available endpoint list is:
+
+- `/v1/update` - triggers an update for all of the containers monitored by this Watchtower instance.
+
+---
+
+To enable this mode, use the flag `--http-api`. For example, in a Docker Compose config file:
+
+```json
+version: '3'
+
+services:
+ app-monitored-by-watchtower:
+ image: myapps/monitored-by-watchtower
+ labels:
+ - "com.centurylinklabs.watchtower.enable=true"
+
+ watchtower:
+ image: containrrr/watchtower
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ command: --debug --http-api
+ environment:
+ - WATCHTOWER_HTTP_API_TOKEN=mytoken
+ labels:
+ - "com.centurylinklabs.watchtower.enable=false"
+ ports:
+ - 8080:8080
+```
+
+Notice that there is an environment variable named WATCHTOWER_HTTP_API_TOKEN. To prevent external services from accidentally triggering image updates, all of the requests have to contain a "Token" field, valued as the token defined in WATCHTOWER_HTTP_API_TOKEN, in their headers. In this case, there is a port bind to the host machine, allowing to request localhost:8080 to reach Watchtower. The following `curl` command would trigger an image update:
+
+```bash
+curl -H "Token: mytoken" localhost:8080/v1/update
+```
\ No newline at end of file
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index d8c7dff..74c7bb4 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -113,6 +113,18 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"",
viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
+
+ flags.BoolP(
+ "http-api",
+ "",
+ viper.GetBool("WATCHTOWER_HTTP_API"),
+ "Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request")
+
+ flags.StringP(
+ "http-api-token",
+ "",
+ viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
+ "Sets an authentication token to HTTP API requests.")
}
// RegisterNotificationFlags that are used by watchtower to send notifications
diff --git a/pkg/api/api.go b/pkg/api/api.go
new file mode 100644
index 0000000..f0e4b4e
--- /dev/null
+++ b/pkg/api/api.go
@@ -0,0 +1,61 @@
+package api
+
+import (
+ "os"
+ "net/http"
+ "errors"
+ log "github.com/sirupsen/logrus"
+ "io"
+)
+
+var (
+ lock chan bool
+)
+
+func init() {
+ lock = make(chan bool, 1)
+ lock <- true
+}
+
+func SetupHTTPUpdates(apiToken string, updateFunction func()) error {
+ if apiToken == "" {
+ return errors.New("API token is empty or has not been set. Not starting API.")
+ }
+
+ log.Println("Watchtower HTTP API started.")
+
+ http.HandleFunc("/v1/update", func(w http.ResponseWriter, r *http.Request){
+ log.Info("Updates triggered by HTTP API request.")
+
+ _, err := io.Copy(os.Stdout, r.Body)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ if r.Header.Get("Token") != apiToken {
+ log.Println("Invalid token. Not updating.")
+ return
+ }
+
+ log.Println("Valid token found. Attempting to update.")
+
+ select {
+ case chanValue := <- lock:
+ defer func() { lock <- chanValue }()
+ updateFunction()
+ default:
+ log.Debug("Skipped. Another update already running.")
+ }
+
+
+ })
+
+ return nil
+}
+
+func WaitForHTTPUpdates() error {
+ log.Fatal(http.ListenAndServe(":8080", nil))
+ os.Exit(0)
+ return nil
+}
\ No newline at end of file
From 7b0d311799b917ec1aed2bc6a1b39659307a06e0 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Mon, 20 Apr 2020 16:18:28 +0200
Subject: [PATCH 295/761] docs: add victorcmoura as a contributor (#509)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Simon Aronsson
---
.all-contributorsrc | 4 +++-
README.md | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 9ade4f4..e67f57e 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -474,7 +474,9 @@
"avatar_url": "https://avatars1.githubusercontent.com/u/26290053?v=4",
"profile": "https://github.com/victorcmoura",
"contributions": [
- "test"
+ "test",
+ "code",
+ "doc"
]
},
{
diff --git a/README.md b/README.md
index b459e1b..ee1f468 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Oliver Cervera đ |
-  Victor Moura â ī¸ |
+  Victor Moura â ī¸ đģ đ |
 Maximilian Brandau đģ â ī¸ |
 Andrew đ |
 sixcorners đ |
From b32cb5d35d03ed15d3ab9c11f1e5d2d7fdffffea Mon Sep 17 00:00:00 2001
From: Valentine Zavadsky
Date: Fri, 24 Apr 2020 14:41:04 +0300
Subject: [PATCH 296/761] Add ability to overrider depending containers with
special label (#489)
* Add ability to overrider depending containers with special label
* Add documentation of how to override container dependencies with special label
---
docs/linked-containers.md | 4 ++-
pkg/container/container.go | 7 +++++
pkg/container/container_test.go | 53 +++++++++++++++++++++++++++++++++
pkg/container/metadata.go | 1 +
4 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/docs/linked-containers.md b/docs/linked-containers.md
index 6960b5b..240fb97 100644
--- a/docs/linked-containers.md
+++ b/docs/linked-containers.md
@@ -1,3 +1,5 @@
Watchtower will detect if there are links between any of the running containers and ensures that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
-For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
\ No newline at end of file
+For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
+
+If you want to override existing links you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma.
diff --git a/pkg/container/container.go b/pkg/container/container.go
index fb495fe..bc2f600 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -95,6 +95,13 @@ func (c Container) Enabled() (bool, bool) {
func (c Container) Links() []string {
var links []string
+ dependsOnLabelValue := c.getLabelValueOrEmpty(dependsOnLabel)
+
+ if dependsOnLabelValue != "" {
+ links := strings.Split(dependsOnLabelValue, ",")
+ return links
+ }
+
if (c.containerInfo != nil) && (c.containerInfo.HostConfig != nil) {
for _, link := range c.containerInfo.HostConfig.Links {
name := strings.Split(link, ":")[0]
diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go
index fe838f7..4f0f544 100644
--- a/pkg/container/container_test.go
+++ b/pkg/container/container_test.go
@@ -181,6 +181,42 @@ var _ = Describe("the container", func() {
Expect(imageName).To(Equal(name + ":latest"))
})
})
+
+ When("fetching container links", func() {
+ When("the depends on label is present", func() {
+ It("should fetch depending containers from it", func() {
+ c = mockContainerWithLabels(map[string]string{
+ "com.centurylinklabs.watchtower.depends-on": "postgres",
+ })
+ links := c.Links()
+ Expect(links).To(SatisfyAll(ContainElement("postgres"), HaveLen(1)))
+ })
+ It("should fetch depending containers if there are many", func() {
+ c = mockContainerWithLabels(map[string]string{
+ "com.centurylinklabs.watchtower.depends-on": "postgres,redis",
+ })
+ links := c.Links()
+ Expect(links).To(SatisfyAll(ContainElement("postgres"), ContainElement("redis"), HaveLen(2)))
+ })
+ It("should fetch depending containers if label is blank", func() {
+ c = mockContainerWithLabels(map[string]string{
+ "com.centurylinklabs.watchtower.depends-on": "",
+ })
+ links := c.Links()
+ Expect(links).To(HaveLen(0))
+ })
+ })
+ When("the depends on label is not present", func() {
+ It("should fetch depending containers from host config links", func() {
+ c = mockContainerWithLinks([]string{
+ "redis:test-containrrr",
+ "postgres:test-containrrr",
+ })
+ links := c.Links()
+ Expect(links).To(SatisfyAll(ContainElement("redis"), ContainElement("postgres"), HaveLen(2)))
+ })
+ })
+ })
})
})
@@ -190,6 +226,23 @@ func mockContainerWithImageName(name string) *Container {
return container
}
+func mockContainerWithLinks(links []string) *Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: "container_id",
+ Image: "image",
+ Name: "test-containrrr",
+ HostConfig: &container.HostConfig{
+ Links: links,
+ },
+ },
+ Config: &container.Config{
+ Labels: map[string]string{},
+ },
+ }
+ return NewContainer(&content, nil)
+}
+
func mockContainerWithLabels(labels map[string]string) *Container {
content := types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
diff --git a/pkg/container/metadata.go b/pkg/container/metadata.go
index fe5a055..2c1b933 100644
--- a/pkg/container/metadata.go
+++ b/pkg/container/metadata.go
@@ -4,6 +4,7 @@ const (
watchtowerLabel = "com.centurylinklabs.watchtower"
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
enableLabel = "com.centurylinklabs.watchtower.enable"
+ dependsOnLabel = "com.centurylinklabs.watchtower.depends-on"
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
From 00715e46333175a62754c26691acb18402ea5fa7 Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Fri, 24 Apr 2020 13:41:19 +0200
Subject: [PATCH 297/761] docs: add Saicheg as a contributor (#512)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 11 +++++++++++
README.md | 1 +
2 files changed, 12 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index e67f57e..553c88e 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -534,6 +534,17 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "Saicheg",
+ "name": "Valentine Zavadsky",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/624999?v=4",
+ "profile": "https://rubyroidlabs.com/",
+ "contributions": [
+ "code",
+ "doc",
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index ee1f468..cc6f1b1 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 PatSki123 đ |
+  Valentine Zavadsky đģ đ â ī¸ |
From 4672811983912a12711cc43a66b0b7eeee8bd159 Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 24 Apr 2020 13:45:24 +0200
Subject: [PATCH 298/761] fix linting and formatting
---
internal/actions/actions_suite_test.go | 6 ++---
internal/actions/mocks/client.go | 8 +++---
internal/actions/update.go | 2 +-
internal/actions/update_test.go | 6 ++---
pkg/api/api.go | 36 ++++++++++++++------------
pkg/lifecycle/lifecycle.go | 1 -
pkg/notifications/notifier.go | 2 +-
7 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index 5bfbcdd..3a51bfa 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -8,8 +8,8 @@ import (
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
- cli "github.com/docker/docker/client"
"github.com/docker/docker/api/types"
+ cli "github.com/docker/docker/client"
. "github.com/containrrr/watchtower/internal/actions/mocks"
. "github.com/onsi/ginkgo"
@@ -35,7 +35,7 @@ var _ = Describe("the actions package", func() {
removeVolumes := false
client = CreateMockClient(
- &TestData {},
+ &TestData{},
dockerClient,
pullImages,
removeVolumes,
@@ -143,4 +143,4 @@ func createMockContainer(id string, name string, image string, created time.Time
},
}
return *container.NewContainer(&content, nil)
-}
\ No newline at end of file
+}
diff --git a/internal/actions/mocks/client.go b/internal/actions/mocks/client.go
index 2145484..33c196d 100644
--- a/internal/actions/mocks/client.go
+++ b/internal/actions/mocks/client.go
@@ -20,8 +20,8 @@ type MockClient struct {
// TestData is the data used to perform the test
type TestData struct {
TriedToRemoveImageCount int
- NameOfContainerToKeep string
- Containers []container.Container
+ NameOfContainerToKeep string
+ Containers []container.Container
}
// TriedToRemoveImage is a test helper function to check whether RemoveImageByID has been called
@@ -31,7 +31,7 @@ func (testdata *TestData) TriedToRemoveImage() bool {
// CreateMockClient creates a mock watchtower Client for usage in tests
func CreateMockClient(data *TestData, api cli.CommonAPIClient, pullImages bool, removeVolumes bool) MockClient {
- return MockClient {
+ return MockClient{
data,
api,
pullImages,
@@ -56,6 +56,7 @@ func (client MockClient) StopContainer(c container.Container, d time.Duration) e
func (client MockClient) StartContainer(c container.Container) (string, error) {
return "", nil
}
+
// RenameContainer is a mock method
func (client MockClient) RenameContainer(c container.Container, s string) error {
return nil
@@ -81,4 +82,3 @@ func (client MockClient) ExecuteCommand(containerID string, command string, time
func (client MockClient) IsContainerStale(c container.Container) (bool, error) {
return true, nil
}
-
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 1694c59..89aa245 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -80,7 +80,7 @@ func stopStaleContainer(container container.Container, client container.Client,
return
}
}
-
+
if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
diff --git a/internal/actions/update_test.go b/internal/actions/update_test.go
index 3c9befe..62945dc 100644
--- a/internal/actions/update_test.go
+++ b/internal/actions/update_test.go
@@ -13,7 +13,6 @@ import (
. "github.com/onsi/gomega"
)
-
var _ = Describe("the update action", func() {
var dockerClient cli.CommonAPIClient
var client MockClient
@@ -25,7 +24,6 @@ var _ = Describe("the update action", func() {
cli.WithHTTPClient(server.Client()))
})
-
When("watchtower has been instructed to clean up", func() {
BeforeEach(func() {
pullImages := false
@@ -60,7 +58,7 @@ var _ = Describe("the update action", func() {
When("there are multiple containers using the same image", func() {
It("should only try to remove the image once", func() {
- err := actions.Update(client, types.UpdateParams{ Cleanup: true })
+ err := actions.Update(client, types.UpdateParams{Cleanup: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
})
@@ -76,7 +74,7 @@ var _ = Describe("the update action", func() {
time.Now(),
),
)
- err := actions.Update(client, types.UpdateParams{ Cleanup: true })
+ err := actions.Update(client, types.UpdateParams{Cleanup: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
})
diff --git a/pkg/api/api.go b/pkg/api/api.go
index f0e4b4e..a34b61c 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -1,15 +1,16 @@
package api
import (
- "os"
- "net/http"
"errors"
- log "github.com/sirupsen/logrus"
"io"
+ "net/http"
+ "os"
+
+ log "github.com/sirupsen/logrus"
)
var (
- lock chan bool
+ lock chan bool
)
func init() {
@@ -17,16 +18,17 @@ func init() {
lock <- true
}
+// SetupHTTPUpdates configures the endopint needed for triggering updates via http
func SetupHTTPUpdates(apiToken string, updateFunction func()) error {
if apiToken == "" {
- return errors.New("API token is empty or has not been set. Not starting API.")
+ return errors.New("api token is empty or has not been set. not starting api")
}
-
+
log.Println("Watchtower HTTP API started.")
- http.HandleFunc("/v1/update", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/v1/update", func(w http.ResponseWriter, r *http.Request) {
log.Info("Updates triggered by HTTP API request.")
-
+
_, err := io.Copy(os.Stdout, r.Body)
if err != nil {
log.Println(err)
@@ -39,23 +41,23 @@ func SetupHTTPUpdates(apiToken string, updateFunction func()) error {
}
log.Println("Valid token found. Attempting to update.")
-
+
select {
- case chanValue := <- lock:
- defer func() { lock <- chanValue }()
- updateFunction()
- default:
- log.Debug("Skipped. Another update already running.")
+ case chanValue := <-lock:
+ defer func() { lock <- chanValue }()
+ updateFunction()
+ default:
+ log.Debug("Skipped. Another update already running.")
}
-
})
-
+
return nil
}
+// WaitForHTTPUpdates starts the http server and listens for requests.
func WaitForHTTPUpdates() error {
log.Fatal(http.ListenAndServe(":8080", nil))
os.Exit(0)
return nil
-}
\ No newline at end of file
+}
diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go
index 9311355..2d2d50c 100644
--- a/pkg/lifecycle/lifecycle.go
+++ b/pkg/lifecycle/lifecycle.go
@@ -88,4 +88,3 @@ func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
log.Error(err)
}
}
-
diff --git a/pkg/notifications/notifier.go b/pkg/notifications/notifier.go
index fc6a310..6595b22 100644
--- a/pkg/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -29,7 +29,7 @@ func NewNotifier(c *cobra.Command) *Notifier {
// Parse types and create notifiers.
types, err := f.GetStringSlice("notifications")
if err != nil {
- log.WithField("could not read notifications argument", log.Fields{ "Error": err }).Fatal()
+ log.WithField("could not read notifications argument", log.Fields{"Error": err}).Fatal()
}
for _, t := range types {
var tn ty.Notifier
From 3e069a707e56e97c951649c730e6e6189494a94f Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Sat, 25 Apr 2020 21:56:57 +0200
Subject: [PATCH 299/761] add automatic issue locking
---
.devbots/lock-issue.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 .devbots/lock-issue.yml
diff --git a/.devbots/lock-issue.yml b/.devbots/lock-issue.yml
new file mode 100644
index 0000000..1afd922
--- /dev/null
+++ b/.devbots/lock-issue.yml
@@ -0,0 +1,4 @@
+enabled: true
+comment: >
+ To avoid important communication to get lost in a closed issues no one monitors, I'll go ahead and lock this issue.
+ If you want to continue the discussion, please open a new issue. Thank you! đđŧ
From 006c4210fab595adeac9b5d8314ad89784225355 Mon Sep 17 00:00:00 2001
From: Victor Moura
Date: Fri, 1 May 2020 07:31:48 -0300
Subject: [PATCH 300/761] Increases stopContainer timeout from 60 seconds to
10min (#526)
---
internal/actions/check.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/actions/check.go b/internal/actions/check.go
index 785701f..dec9f60 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -45,7 +45,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
allContainersExceptLast := containers[0 : len(containers)-1]
for _, c := range allContainersExceptLast {
- if err := client.StopContainer(c, 60); err != nil {
+ if err := client.StopContainer(c, 600); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
stopErrors++
From 291627dedb6cf7d57464df9d6a8fb40e11bfc3ab Mon Sep 17 00:00:00 2001
From: Simon Aronsson
Date: Fri, 1 May 2020 13:13:14 +0200
Subject: [PATCH 301/761] add logo to repo
---
gopher-watchtower.png | Bin 0 -> 152602 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 gopher-watchtower.png
diff --git a/gopher-watchtower.png b/gopher-watchtower.png
new file mode 100644
index 0000000000000000000000000000000000000000..7cf5233eaf44ff718f2680a6bcd6a8f4331cad63
GIT binary patch
literal 152602
zcmeEuWmr^O8z_#TAWE0Cg>;B?je$xd-66{9{GG0N#+1x4a)@Nao<594#snBLOn3*W0=@L;@NU#yfBmqV=deZhHkFv4E{fM+Pu
z`9Sud=rGFqT&xbajwNXtD!iCh7H?s(`kk+j!N@KOto_`1Ed;YC1L4>2-#yOJ%kIRi
zl#{_ncY>=O(%jt-G=;-zL#)od_%1CuCQ>v_8w2mc%uxD(fs%urH)~+vU0ZZTuNajH
zXWV%-lKh%uP*rBp+JGX{_+0idARl`JAERqtG*AGANGh=;d@IH#m_Yn
zS~$>B2|Fq{?R>CwdzB+HGBk4Y@P_^;ej3k@jFUe#XkHE92=s2_Za8Qj-5UB3O%qYC
z{fgNej)&uCM7Y+tkid-b;VUiMO&jmmr5J}8M0K!FiJ=VZela?fwYQ&?b1~g?@c#J9
z&BQTM+-{6uHK80=mQfjxxjE?Ovc3e{<2zA`!KAkeFtHh
zYUOU37{VXI%_P3iEVUZam!OQhQWiFa$y3H9?)HW*vNDVA!naB))_W*R=l&$;Uwpn9
zwwDk#>g&LQ%kmNZBU+d^k<*VY@%*>OJU)iHdh?enw_KxKC`;aEmSEc?Ph9`|`vt}H7WetImg~C~Rt`H_Q
zj0w9(`xezw3wIcH?#V^GfGY|wIH;)zJeagD(xQ!P2_?fGSF-&^mwdT8j8}NU%!6ZxLq1xm`tuAw2T_=0lIP`=g0dzttnLiu&iTe@mFZBka#P}NY4M%D?kVDFBqa9#B*
z6B9)%!Gv&Fr!2>vZhLj4Xe
z=fgE;6
zO64a_w?YE^Kd^js{ux6{Pr&ZYrFTyFQO`r3hlX+eNu>!gUmnRx8S!&`EcQ~7D1*D+
z-%p0iX31$WEDEtPq8KqB)9&LNCSQD0dzxU3(By~$luc96;NRae?Tasg2VXkfN`COk|
zH(oz8af3G5sn=^a*06T6QoK{5jB;mWI_`9^rwCkrrCp`P-bxa2wU%VyT{C;GewO}{
zfl267u6W~`i+N#2(MB$Luso+cKBJh9u$l6iFEgqgWF1t-T6Y`oP8dEjp6e(~X1n`Q
zu+5gnUjJL8E<4rp@8SC4f5IC$HjOp9$Xi-FSGw?w=Uar6qqe%l0E&8;*=sY;h2-dLjle;bGN-cIotv(=_iygc
z@1*bixWIeC`icTcJ1IZwO;X;gmoD3r=99Ux-r@9SQ!#IUzSghwE7UYnSIu~@?v>Gq
z_6KblL#sXlgPCXHM#Z&jiC0bZ&1qN}*h{prBKSfve{F`^H+^riioO*t&oi5WR_&qp
zUhk=Ki}BB_%lT@?J@6hG7KroTp
zGM&TT+0)>dw`w>k#f-%q#$2m*V3B7bVViF0Dp>1)<=%wgaW!)?BQLmAKxv=o?6E7j
zNr|nF?e{pLR#wVYDthu>^SNL;hS#>C>-7!wVk5S#vo8yLHbobSsH$p*k}8vaC{}aT
z7qoAB#YAi7Xr>QkRg@iC9R{PAz@;lYUzC_IafYb>qQtVftai1V=plwTI{g(VQWGLZ
z46|s=<|~mUrVH^nKX9Ht(WOP#^EAVBCD$hp#@$%u?=Q`A)LV^yt?HSHZtfo|%;=Zw
zv*1*2a`ycJ0n+4_z#E@$-!ug9wd-?M12
zKdL{3Uuw{9kb1~hvx@48l^DIKQ}kSLDU+l0@HJYZ%NS*^6TWxRoueD4qmp?bi=E(?
zQ2n_h3BMJ;Rmw~5;g?VOw|d2bnWqUNd{(Okt}&Co)k;>28uks7(aUl
zzsEjjka06DC(Xa7rKdK8o`sO9th~{r=Z)TZ9ycB#=32%{W^?tp5}jY(gF$IQgsHLe
zUa2aIvzbk*12MBPs(Ej6X>5P77^im#JKwD`=Q$4A~zHbxbgfiLESV*sE(E
z2uwKt*`y#d;hO7RN`Ikfk@q_#t8l5|(LznCkkX;_%8%~E&k__76r76#9(~t)Tgy6h
zjpg54J(YYqlwZ_Gn$KQ9Y~o&^yX?nMZC5?cC&H)4=Pc?W7H)TSt~X~^>`UyLWSFAz
zzS*w3udwHw@<8?WlL)yZ(|w3RLaf
zN)Ai2*IMGDQdzP^cR?GKO`D#p
z9qbX!ADSkjDELCG%{~kF-?0(gcF~+2>GUMlVd*!E=<2j&n;IDxa|>b4{o32CZdc;6
zs~DBRsbO8%X)$oH^HX!qou}u|?-q+6%a7V5CL?}DS@SmAIod>3j9bsPnr_S+
z+uvWXv3qPgR8lf*)+{hsHo4ihP~jG^uC=DUaAPjsqgK@4C+4}}m%Wa)t{qCxX!bI}
zvaXm%{I1=#^Um85YXvO(j?Kx<-WP2yq{Q^`3kghdIjmOi5%059vpn`(aaTNW-DLQv
zc>7>!Z*<3E&f?Jc=WI3?*-%P_z*^IS`s|bLx{sUFi;sl}T;vXw)_+dR?oNg_pO5d+
zT+$RR4|g*?AX=MmQQGMa4ZZP4{2gq{24(p3O_V`$*bCB&DDyNZpSM4*uDybPJVe1q
zJ;0Z0>c<~Jfm5W^)KbN$q09)hSmvXlysj{LAB~cq7nE7?F#MS@`&Azg$>45(U0w4$
zluEAiOY(O(Ig1TU=4Q|3cJHF(J`-ggBSb`z0Go+8u=9)V}a{R64KNU%&qjOIhgM^fWv6lgW)N$*a@H;UlQ1AzplV
zXt<}}J$^bk#P|OX0fzp6M*Kh32-fZkdfPG2xEt%F1Ee2Mvvj70=2;Lb$xW
zX$V^iOD*N(?l!rw_}7GWjr-Q$$&6?$9V)bR3{*Tc4!jz%tr2`xR%4%KwfO0T=6czq
zu;mURuKF~^&MfV~pdh`2{YB>=jmDaP&9ItRlOY
zXhg|x(ZcU~(GU`5TQm@5PhC&fecxEonNIB2nXXd(9g~HCiTzL|_j&Yo*Y$9gZpQFu
z-`i(c+lfzn1KbJ?HRPuNS$rke91&*Chz}E<4^#1zT*Cn){o$UJj_-$9y6BO66VoRMd9Uf^I^u9=jrd=)0m8M%we_BbuCqc*S7W+5-iABr&M*dfIZ5=+-x$F0j
zVo0M1+=AyeVb?J!au{+I##UQPxM6TB2bZYrO}&ZH{up;QM!}zZspXSCaJEZwhv~n9
z?!@Wu+Rf|qjNbvzrHGjCWug$%AIzKfnyz*jak`G=eZp;E5M}ILY)G~}$*Y`dY|F3M
zS()9MikBLV+7x=nHR!}bBOVz!6HOpR-3RNy#+uoh>=>0YP9vh=&Gokn^Z%?^A=Y-H
z(9{xu?{}kuRgn4x$G{K|ThVp-xUIBl5QF8jDYQlVrhH#xLOoD3B;bVt#xqT=b$Nw%
zd0i?u6jbvb_#_Qu7h$~<{ynBl@N;OVGDYFUnZw^kP#ITSQG6f_*Gl>}FpSA~g9)l)ex
z(;P?4vHNg~Fid%4yraNsO%TeeEi<7zI8=2z#w8eoR>-?+$cgKbFDZL%HZvZzv=U;R
z-JQicilggoN0$$T2*+To&V2N1
zb9rN_&&o#!_EomU%{xO$Qk$rYdyy~H(Z?Re?ubkpXJ#9vi4jY#l*e2HkOg}
z#nxP>yeGao)AU4#dwoVQq10>
z?hwntZpT(L*&xmUG|6OKtjZAQ2FzVb%Wr`OtN4cfEj}
zYS)eDjUHvns1E=jLa1aqPI$05I&S>PGsv}+|I6fOX5cFbuZ%m
z@}M(SO#>idx7!|u^$-IUy7m9CeC;K5O
zX%`(Xd8#|oq3BujQA&s^v;06(JVvlgVf705Iy@YeR57r&X7=D#e!o@QmT^*Eyj}y5
zA-!h$k-x2U0r}(VIjacdvu-NK?wHG!Z!UN)_kU%ne&8>Jk53~hYE@(e2!>0d1*^HV
zbX%5GRM(JY5~rpW%iMkbn>uM%ITCpqr!F0cr=VQnHj+Ea#9MY_Qx4lfT_el2F}`u;
z6*24UOiuv2b#S{)en@7^ua~fOsoz$~Gg4+WoqmaYM&P>_!#U6VRN^7@0u&H4_VVSc
z8yCaC4-$X=ZM6yYppR|6-o?O!NV6iUiA^c^ObYZ&ihu5~5Bc_FY5(x((9u0|4YB&b
z*ur_P3L`naLeHwRRj^#&@L;s%cC=r?;681pb04G(3Dj9nC(a3}nwKf3^?lLJ)p+(t
z=DD)}k?z}qXS7pn{a;RX=9`RI6vrjI9;GyUN1@W(NN
zL_C0ZU}5D>)B$;)S)Q;>eJR6Ni3o*T8F8*iiln)L>in6T7l#PCMYFWp9JJi2x>)_S
zSq>mov<0{H08nZoG&Y;ZcZ!
zLL%G)s2v9HZ<;u)k
zNHKHhvv%kiYNAUP$s^42-R_zXGNu}<9{Z9KptIdlO72LE%@LNPHML)*DncC%#`l-2
zv&?78qR}8m#ok5hRC68gW>~dc@#R!z3Fk0Y2}dtJvg$k>9JNkZgET}MEd>GtpQhTn
zS^s-|$)dlMAejQM?`$B1$IA8J%gw2^lqK^7WTv?qca>wWvpDy@f<8k3ECFcA$CqV+
zUW|!rja2iEQ^dYwThS#BJ
z0+%C@Jp2YyRx}|PaGijmeFCT=#p!w@n5Oz)KgQ2ilZmy$8;GyOB
zy8P(Yo1DS1o`3#3_8y*?96pkYx?-T>v!98O;=hUEa
zQ;u+593-$O5tT*^nK%Pjb9iLtInn+flIEA%k^Y?n5YqYfCpE;_Coe1Ag?4UUz^25*
zHn=Z#RCmP6q1HDv=zxCSJAsWoXT~z|J{d#R7>LW#FyMOdxSufr@Ufw2DO9is+xawyqiIr*+xopB+2_A
zna7MXlY&r44yq)3Fys}%u08IPc{#m<|0OaFL_zeR+4bA`OtmIhr$stu`LjS@rVg+H
zde|!U#1*`teTuZH{Y?S9f_}D}Amdc~bgB2qZi;Y~T$VXoOe@NCb}0(HLCsb7i$>un
zL0BgM?8pC&^mBC?l?)(?3_7$?K-r6)y>==R#d9!gf^MVLE4o9jc0L>jxijR8B_Kut
z=3Kf3(%-;RR|v~tMMO(j5@LvJEG}#@Bh5{;@CZvd^&u=>wcy>$jXrj}Zp+!{EuS0@
zFtctTdAk(_Y5;p`4W$0Kq3Sn=;<6mfb`<`p=^Ks07Hn!xv^y?xjBaKvUvT%@-|V~5
zsbTPo)RznCbkI-dpp|N9iQ?OA3x)gTg$EOS@i|sAYPCi8kf@>)XvBiBZux9;UpJ}3
z9DjT~`iaIC{?l^+4^c6Y>8MPms{*zXh!6L@j~{(QcA>Tmn_nS|)C3tzK)xXnTUyG9
z)otM4ojJRZJ?tU)NP|oGD`W?;`9KeQy{}3^kn-@hmiGNesT7BBSPm!CRerI9{uv~%
zCyAAQeP+0xX4w-esd{<>KFALcLqGxDSw=V@0PV`LDJk@nF2lItL2fWj{n4u0ZcqTg>k@>jFwrhw8
zTVO++GbE1)NjWuW6hJ1_Rc7b4zcIuP(wVRk$v{bnve!hwk?o+)0p1}9E|r2rhHa_-
z9@|>_rmRxghhz13!7wSSeq=dyZMS-5zHi)EY!^ZcjtgpUpDETnQ0F1kF!^`ad@Q$x
zGW;_=-6Lkx(~!~t0)1aenwn1Vn99+@X0A{H*EWF=VtZX4F#WFYosj9)LrF`u>!tvK
zqK3Vy+#%Dk>j94~zQkCx^(pM)HaUT+=~rct%*AFjgg%BazjYlb|NEDnG=N4U23E8F
zt-|$*l96D(!>ho5X3pD;9$^p6hwyoCu8570MNGGDb5#6K7@;-AZ?0j`d_p=B0Xk67B<`UE
zL98>uC}mrVDs&mPT^_s`^UPRe_E>XJjTp10-$aeP{ekt4kdIMwl;;A{*erqO`0Tpa
zf+r!iFO}|3lvpmHogZmqDGy&%I)5bgbFdjI*-gyF+c)9GmH}m
zX`~CfOSI!u6Iz(PQu)Dz^LBR=C8W{NM`@Q#3OZ;k=dwMW-%2USGd%+DJp<%^bQMg2
zlJqjPa#BwI9nHI}8avGq>lzJ^J|Iut$DAwu5p3E1)bnAg0u!dX_dXKoT7We9dDUo0
zUGYeGR!R!DJ}`{-5NpuMVQd`vQDipJtX|-M!*7m=iO3}Ss{kX?al9r3`rSFS0I6JY
z^^tkNsSRQ3n}Zx!^Mf29;|b4gZGQl1*eU0HQ&_9NaM`k&
zu&esKKYvi*hxTK)hEo?&`E7Uc>Ok>4nFR3cebJmyYXZ@K37y$`*4P%%Hm0Wa{gueF
zKjP&`^e)w}Z4FL{SO_#IW}e2sv8+3%KJw!HhF=ZgxCpbnDZ~xw^T0D)3AhZrFB}i)
zRWr(05)0=Sc0Vdbho7hi8a^!xiw~ve9F_XUJo)%ItkHwwl`kY)DR5lioCIWDM;Xoq
zb$jE>#p??$=B7f&{D!BX($kaHws=yJI4|T`gIXXV#jYBuov(&l%^}Y$bDcFWhu*Ao
zd82TnhEHc_ZJO?jO4y0GzGL1s?Y-&8HxtmEw~Eygd++6U;?FSX>B;YbT|E+qlBHM+
zn7q8aWncPcjeOS-9`I|I6yHWca)o$6N34`AyvltrwU9kJR3PbiyB5U9A=Xr!&k
z3-~3plp%S?T8hJW=M)D>f`VjayAj83=^X4Q!SkLR;k5-v2a~S@L_Zm&AaY`xz79O%MjquEUb!
z1DUGouurpR^|XNo3Dw@_R~xQRPA6>hM&%sHl8x-0EPP&8_nj_9jpT!9FvWO
zf%61W8AvBogGPM$ttX{34O$2Nd!)31SR!E;sj?Kw?Ya-5;Tn;(sSJR{w4hY}6Gs;Y
ziU8bDHr+@4f(}pAHvDHtLp1s6N*yE>ZXfX*u7VcMWloy3E=ZH#tN%4OKh_LF*%zSv
z+)w5Plw1#whiLe)Hcf*U^rIhqGQFKC9q!JjC&!$SYXuX)nkT(TnOjidu55KXQyZi#U{1vN
zxx5SmhxRJMdC`rPxpXciIVD`-j({<;d;kxZHi2R0DX}(9wXt(Hlwuw==f*TZ>ENu(
z2R{A|d!XsgC&`!U{*II*@fBe%GLk_;U`7NfstE2x*~;Xa4`mdRwp{@<;L!jwyAiaW
zfWtUY!9V5P`Fc5qYmmwBjm?P}Q!iaaP~;CXI>p%7fYNCWds7YRl6HXOnl*op23WmE
z4;jM+HB0q=yUqOyjn(r=27#}^moMEl>1K7>m?GMpD^P`^lml7ERq!#G2^KP<6f*&G
zB@q{?9Qd2&g;?nsl86jm79X`R0otLdP%Rw4_@%b?0-0U-ED5rBh_!>V8po>=z?%r6
zLy_6i35aL*&(Oi2Xv`y>0E|w!l{H~R@o6!vOt;5QEJ$Ir7;POoh)ht|
zi9>OK{H0l$bGh!76VHW1s}6P5kcK;{!56~p(FD02Y{=~_iQj_9uAyqUP3LisudRZ?
z#LtATQoj?EN1eHuCD&ISf4H4gThEXweH3?M=jZf7DOHLr@j_2zYz+GpTspa(D?0!gTs(k>Fa^M|iYYKF|bZ1-u@M8v~m
zHi07345n&Yt{oZXxKrr4*(gy5x%G$W%=<*ZxU{ffFU`xin2SexdHOBAj&&c%zz*qN
zm6M`yR87ka?TB(O0j_&P`P#FKDHJf&ncJ*$4Vj~b8U|G%n!d?H_w|vjz9Z4ZRcdx>
z+-MS7%zfUGqIboB1d5`qAOHu~@XUSyl0gS8G=U3hZtDFuy8+wpY>wJvkXwasppkN#
zkd*oJ@o#jmF7{~ldjlRl`2rHIgo{Zgpi^*rsi}{u3gdPZ;gJ9SHD@1{Q&>}=DyYP%
ztJ!?Jn!R?%be6S`0MJSh0@zg8B+CWZbU${7?$3Tx(M?LHxiFQ+J115P056~wQ?=hJ
zrpkARx^9~hJxC`EA~4OGRv_qWo1q=7e`jc!Y{b-A8Kbf;dH;8e1D^X-Yq`3ws(Lbt
zGBsl`&W|A!qSywQUz@92%58XTFbw~;grBSw50{3E?QzW?pzBqj09?8A1Fd@Y(S>Wt`_~PbrgPTue+V_Jfs`e&In9
z$^a57GKjweRO8yx^zE2hjIw5LkWH)l%L9`-(Z&EZgwUa3P~q?%`zO$Q`^h8&HNlgC
z3FJ^ZBm$uP$Wi7kkmeK&?Q5-g%iB`O;)4!&?(Dy
z5vT=amHSffuZuwcET%Q!XIvvpyuLoRY(o)Ygn<+K5*RraEi>q2Vj*4WPPDT!WCRpw
zsUQBcx@gpJPE))PHH$|!D)v$Q^Uuiu+n;Fyk+{k-0Pg4_V%$^S;X7~o5^G)!AB}>L
zI_3}c0IX!OR6WvWrKJV*X^Qs0=XJ549PFuxUbvRbTs&ZVgx6*3UnuP46T7e@Ajz
zq5W;=RXswb7)jiauS)M<((4VLx(+IY&Y|CzfKJ@gs(E+S@~5z3wWH@<@eWCT#U8-_i6bz}YQh@t3lDgYsTiTbx?ddFz1
zShL^6uLwdddsRr9e`5wb8tqO0uJl@o7ChQMkK#WK7$Jn(NyIVkDkfRlNh78*2Ee>u
z1LdLhdy0?hEj66fc;3NJ=+BzNcwT{bCJ)7b!hG_gvWd^+YErb58q_$BCA|gIdDtH)
zkWhHx{TFZqn#N&sgyo!a%(>_2>N$(amNqZ9SRAe`!nSG5t)YJR1V#0Y9$c_5OM+nMGt
z4E!yc)#0cceu6x2{7Hhc>I0F9JMv`gT~w%-S-6*DtrPc}h|%{AluwQTh+ab1(eP;9
z5G4zxLQNH0^&{qTo_~R%;xYd&{KU^ZWiM({JbeKpQ_k^pn82fFbzBLYa_l#RYGizs
zxSSI#!M74Cu~f~83^oK>UOJ1eZrMsuvjOH*k-xr=9vEENz*0tZJJQ8YPDr5tWZnqr
z-cNj`&+FeU$HynDy(xmFG%V?_!((rP0DjA|G5lr#IntbqCe=tc1=gNfdCOq@6(&^L
zPFX(h;@%2yopc(y0y!U1py>?Cvv^QKqpj&i;b}lw$qwx7R6Pqhg0V|!<@Mg|RQ+u!
z@(p7|Ux3r`h6YYHk5jIJyt)t!SVkfb&VNvjO!<#MoX||TqocB#TZ=N(ZVx-ER0FPn
zsLx4|e(wd;0FsKKOp3D8;NKa;w5AwER@ui+ZyJ|->qXh8$Nn!{WwS&2!1bWKnqipE%Ill^|E3FUad=1~u-M$DSV%VAg2t!J3^{*=7U8Q|D@c4hKpceLLPJ<iwUy!$~ec
zwr3SJJA^=rudtZM2#Lt(+_!|G^B@>7ErH4AqfEichwJ!NuK(F1wRAD;R|J=@@dY&;
zC}!H=+DN{DcLL2(j?cwGv=U4Wxoh>iZN5zCDa|+qE;Js#IEv*{wlQeyx2Xp85hQ?B
za0K1)1D#G(s2`3cwD-zk@}<8)$pYFbwxFgU+u7Wk{eqwyegW#~+d}$0`5J{L%t_HVR
z-QGY$LWto}Q$?B=9B$1dMtBjq!SX*gqma)!%)Wbgp%PNFeZRVxRAGBNDzG5AN
zg1`ffx!|Qg#!;31s3|EaBU_`*U<7uRx8PdF8GwUFF{^d5+bL5Qkl+ofU**Wj6b6_r
z5fnV8?CP@FjMln&GqsPKkw+2Bh(4>go}sEkaor!pj}9AeOW?rXf;jLHOCduCrX@1|QjJ97l;IVZ;1%&Nld#_0oBibYdSSZ~_ii*WKJWqrH
z9aJXdPU{nF+0WYlTAWx7@}wl-H=-sJjX+yA*$ur}+jG;KYiOI5VOIHQ%5