From e584f8bfcfd1be713afe7c1bbc52f902d2163900 Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sun, 12 May 2019 09:29:52 +0200 Subject: [PATCH 01/61] 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 02/61] 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 @@

+[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#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)): + + + +
Florian
Florian

πŸ‘€ πŸ“–
+ + + +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 03/61] 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 -
Florian
Florian

πŸ‘€ πŸ“–
+ +
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
From ed43d70e11fc8afe520c675d5046d5dc23adb3b2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" Date: Fri, 24 May 2019 17:21:51 +0200 Subject: [PATCH 04/61] 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 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors)

@@ -351,8 +351,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
+
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
stffabi
stffabi

πŸ’»
From f80d8541fded0dec0eae0db065f3fbe33614649a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" Date: Fri, 24 May 2019 17:25:16 +0200 Subject: [PATCH 05/61] 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 -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
stffabi
stffabi

πŸ’»
+ +
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Ross Cadogan
Ross Cadogan

πŸ’»
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
stffabi
stffabi

πŸ’»
From c722730604b72dd9f6d8e71122daf4c1469b0396 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" Date: Fri, 24 May 2019 17:27:23 +0200 Subject: [PATCH 06/61] 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)): - - - -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Ross Cadogan
Ross Cadogan

πŸ’»
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
stffabi
stffabi

πŸ’»
- 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 07/61] 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 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors)

@@ -36,6 +35,9 @@ Join the chat at https://gitter.im/containrrr/watchtower + + All Contributors +

## Overview @@ -349,6 +351,10 @@ docker run -d \ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + +
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’»
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

🚧
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

🚧
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
+ 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 08/61] 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 09/61] 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 10/61] 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 @@ Join the chat at https://gitter.im/containrrr/watchtower - All Contributors + All Contributors

@@ -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 11/61] 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 12/61] 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 -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’»
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

🚧
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

🚧
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
+
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
From 22dc77efb27d057d600308ceccaf52c1cfad4a70 Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sat, 25 May 2019 13:46:41 +0200 Subject: [PATCH 13/61] 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArgumentEnvironment VariableTypeDefaultDescription
--help---Shows documentation about the supported flags
--cleanupWATCHTOWER_CLEANUPBooleanfalse + 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-Booleanfalse + Enable debug mode with verbose logging. +
--host, -hDOCKER_HOSTString"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-stoppedWATCHTOWER_INCLUDE_STOPPEDBooleanfalse + Will also include created and exited containers. +
---interval, -iWATCHTOWER_POLL_INTERVALInteger300 + Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. +
--label-enableWATCHTOWER_LABEL_ENABLEBooleanFalse + Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true. +
--monitor-onlyWATCHTOWER_MONITOR_ONLYBooleanFalse + Will only monitor for new images, not update the containers. +
--no-pullWATCHTOWER_NO_PULLBooleanFalse + 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-onceWATCHTOWER_RUN_ONCEBooleanFalse + Run an update attempt against a container name list one time immediately and exit. +
--schedule, -sWATCHTOWER_SCHEDULEString- + [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-timeoutWATCHTOWER_TIMEOUTDuration10s + 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. +
--tlsverifyDOCKER_TLS_VERIFYBooleanfalse + 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 14/61] 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 +``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArgumentEnvironment VariableTypeDefaultDescription
--help---Shows documentation about the supported flags
--cleanupWATCHTOWER_CLEANUPBooleanfalse - 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-Booleanfalse - Enable debug mode with verbose logging. -
--host, -hDOCKER_HOSTString"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-stoppedWATCHTOWER_INCLUDE_STOPPEDBooleanfalse - Will also include created and exited containers. -
---interval, -iWATCHTOWER_POLL_INTERVALInteger300 - Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. -
--label-enableWATCHTOWER_LABEL_ENABLEBooleanFalse - Watch containers where the `com.centurylinklabs.watchtower.enable` label is set to true. -
--monitor-onlyWATCHTOWER_MONITOR_ONLYBooleanFalse - Will only monitor for new images, not update the containers. -
--no-pullWATCHTOWER_NO_PULLBooleanFalse - 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-onceWATCHTOWER_RUN_ONCEBooleanFalse - Run an update attempt against a container name list one time immediately and exit. -
--schedule, -sWATCHTOWER_SCHEDULEString- - [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-timeoutWATCHTOWER_TIMEOUTDuration10s - 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. -
--tlsverifyDOCKER_TLS_VERIFYBooleanfalse - 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 15/61] 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 -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
+
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
Ansem93
Ansem93

πŸ“–
From de844895cb130c32f9a26769cafee39fb148f711 Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sat, 25 May 2019 14:37:47 +0200 Subject: [PATCH 16/61] 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 17/61] 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 18/61] 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 19/61] 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 20/61] 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 21/61] 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. +

+ + Circle CI + + + GoDoc + + + Microbadger + + + Go Report Card + + + latest version + + + Apache-2.0 License + + + Codacy Badge + + + Codacy Badge + + + Join the chat at https://gitter.im/containrrr/watchtower + + + All Contributors + +

+ +## 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 22/61] 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 23/61] 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 -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
Ansem93
Ansem93

πŸ“–
+
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
Ansem93
Ansem93

πŸ“–
Zois Pagoulatos
Zois Pagoulatos

πŸ’»
From 635e295ff41dad3bf71fbf96bb20e58f58760fe9 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" Date: Thu, 13 Jun 2019 20:46:52 +0000 Subject: [PATCH 24/61] 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 25/61] 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 26/61] 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 27/61] 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 28/61] 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 29/61] 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 30/61] 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 31/61] 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 32/61] 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 33/61] 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 34/61] 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 35/61] 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 36/61] 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 37/61] 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 38/61] 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 39/61] 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 40/61] 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 41/61] 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 -
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
Ansem93
Ansem93

πŸ“–
Zois Pagoulatos
Zois Pagoulatos

πŸ’»
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
James
James

⚠️ πŸ€”
Florian
Florian

πŸ‘€ πŸ“–
Brian DeHamer
Brian DeHamer

πŸ’» 🚧
Ross Cadogan
Ross Cadogan

πŸ’»
stffabi
stffabi

πŸ’» 🚧
Austin
Austin

πŸ“–
David Gardner
David Gardner

πŸ‘€ πŸ“–
Tanguy β§“ Herrmann
Tanguy β§“ Herrmann

πŸ’»
Rodrigo Damazio Bovendorp
Rodrigo Damazio Bovendorp

πŸ’» πŸ“–
Ryan Kuba
Ryan Kuba

πŸš‡
cnrmck
cnrmck

πŸ“–
Harry Walter
Harry Walter

πŸ’»
Robotex
Robotex

πŸ“–
Gerald Pape
Gerald Pape

πŸ“–
fomk
fomk

πŸ’»
Sven Gottwald
Sven Gottwald

πŸš‡
techknowlogick
techknowlogick

πŸ’»
waja
waja

πŸ“–
Scott Albertson
Scott Albertson

πŸ“–
Jason Huddleston
Jason Huddleston

πŸ“–
Napster
Napster

πŸ’»
Maxim
Maxim

πŸ’» πŸ“–
Max Schmitt
Max Schmitt

πŸ“–
cron410
cron410

πŸ“–
Paulo Henrique
Paulo Henrique

πŸ“–
Kaleb Elwert
Kaleb Elwert

πŸ“–
Bill Butler
Bill Butler

πŸ“–
Mario Tacke
Mario Tacke

πŸ’»
Mark Woodbridge
Mark Woodbridge

πŸ’»
Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€
Ansem93
Ansem93

πŸ“–
Zois Pagoulatos
Zois Pagoulatos

πŸ’»
Luka Peschke
Luka Peschke

πŸ’» πŸ“–
From dff16dc639797496920a274b76d2aeb63683cb5f Mon Sep 17 00:00:00 2001 From: Luka Peschke Date: Mon, 22 Jul 2019 21:17:54 +0200 Subject: [PATCH 42/61] 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 43/61] 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 44/61] 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 45/61] 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
Mark Woodbridge

πŸ’» Simon Aronsson
Simon Aronsson

πŸ’» 🚧 πŸ‘€ Ansem93
Ansem93

πŸ“– - Zois Pagoulatos
Zois Pagoulatos

πŸ’» Luka Peschke
Luka Peschke

πŸ’» πŸ“– + Zois Pagoulatos
Zois Pagoulatos

πŸ’» + Alexandre Menif
Alexandre Menif

πŸ’» From ab921407ab0390f472911982cb7631398f54a6ac Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jul 2019 23:42:40 +0400 Subject: [PATCH 46/61] 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 47/61] 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
Luka Peschke

πŸ’» πŸ“– Zois Pagoulatos
Zois Pagoulatos

πŸ’» Alexandre Menif
Alexandre Menif

πŸ’» + Andrey
Andrey

πŸ“– From b05cb17c99e85e801edf4efcab2482554a8a6b8d Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Fri, 2 Aug 2019 14:13:47 +0200 Subject: [PATCH 48/61] 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 49/61] 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 @@ All Contributors + + Pulls from DockerHub +

## 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 50/61] 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 51/61] 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
Alexandre Menif

πŸ’» Andrey
Andrey

πŸ“– + + Armando LΓΌscher
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 52/61] 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 53/61] 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
Armando LΓΌscher

πŸ“– + Ryan Budke
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 54/61] 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 55/61] 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 56/61] 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 57/61] 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
Armando LΓΌscher

πŸ“– Ryan Budke
Ryan Budke

πŸ“– + Kaloyan Raev
Kaloyan Raev

πŸ’» ⚠️ From bea8b9228f9f5c793e3b6fda0aeb456a7bdbdb2a Mon Sep 17 00:00:00 2001 From: sixth Date: Sun, 25 Aug 2019 06:43:03 -0400 Subject: [PATCH 58/61] 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 59/61] 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
Armando LΓΌscher

πŸ“– Ryan Budke
Ryan Budke

πŸ“– Kaloyan Raev
Kaloyan Raev

πŸ’» ⚠️ + sixth
sixth

πŸ“– From 17cbf86d486ad085b36191c518385a7a3b1f2b9b Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sun, 25 Aug 2019 13:02:08 +0200 Subject: [PATCH 60/61] 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 61/61] 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 }