diff --git a/.all-contributorsrc b/.all-contributorsrc
index dfaa6e3..a782e6a 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -277,11 +277,12 @@
"login": "simskij",
"name": "Simon Aronsson",
"avatar_url": "https://avatars0.githubusercontent.com/u/1596025?v=4",
- "profile": "http://www.arcticbit.se",
+ "profile": "http://simme.dev",
"contributions": [
"code",
"maintenance",
- "review"
+ "review",
+ "doc"
]
},
{
@@ -309,7 +310,8 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/21138205?v=4",
"profile": "https://github.com/zoispag",
"contributions": [
- "code"
+ "code",
+ "review"
]
},
{
@@ -393,6 +395,42 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "jnidzwetzki",
+ "name": "Jan Kristof Nidzwetzki",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/5753622?v=4",
+ "profile": "https://achfrag.net",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "mindrunner",
+ "name": "lukas",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/1413542?v=4",
+ "profile": "https://www.lukaselsner.de",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "codingCoffee",
+ "name": "Ameya Shenoy",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/13611153?v=4",
+ "profile": "https://codingcoffee.dev",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "raymondelooff",
+ "name": "Raymon de Looff",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/9716806?v=4",
+ "profile": "https://github.com/raymondelooff",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
@@ -400,5 +438,6 @@
"projectOwner": "containrrr",
"repoType": "github",
"repoHost": "https://github.com",
- "commitConvention": "none"
+ "commitConvention": "none",
+ "skipCi": true
}
diff --git a/.github/workflows/publish-dev-dockerimage.yaml b/.github/workflows/publish-dev-dockerimage.yaml
new file mode 100644
index 0000000..c02186a
--- /dev/null
+++ b/.github/workflows/publish-dev-dockerimage.yaml
@@ -0,0 +1,23 @@
+name: Docker image (latest-dev)
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: jerray/publish-docker-action@master
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_PASSWORD }}
+ file: dockerfiles/Dockerfile.self-contained
+ repository: containrrr/watchtower
+ tags: latest-dev
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index db50329..82b1b1b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,4 +25,14 @@ go build # compiles and packages an executable bin
go test ./... -v # runs tests with verbose output
./watchtower # runs the application (outside of a container)
```
+
If you dont have it enabled, you'll either have to prefix each command with `GO111MODULE=on` or run `export GO111MODULE=on` before running the commands. [You can read more about modules here.](https://github.com/golang/go/wiki/Modules)
+
+To build a Watchtower image of your own, use the self-contained Dockerfiles. As the main Dockerfile, they can be found in `dockerfiles/`:
+- `dockerfiles/Dockerfile.dev-self-contained` will build an image based on your current local Watchtower files.
+- `dockerfiles/Dockerfile.self-contained` will build an image based on current Watchtower's repository on GitHub.
+
+e.g.:
+```bash
+sudo docker build . -f dockerfiles/Dockerfile.dev-self-contained -t containrrr/watchtower # to build an image from local files
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index eb53ce9..6e42f6f 100644
--- a/README.md
+++ b/README.md
@@ -62,64 +62,73 @@ The full documentation is available at https://containrrr.github.io/watchtower.
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
+
+
+
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/cmd/root.go b/cmd/root.go
index 453196a..63a824c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "github.com/containrrr/watchtower/pkg/filters"
"os"
"os/signal"
"strconv"
@@ -108,7 +109,7 @@ func PreRun(cmd *cobra.Command, args []string) {
// Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) {
- filter := container.BuildFilter(names, enableLabel)
+ filter := filters.BuildFilter(names, enableLabel)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
if runOnce {
@@ -155,7 +156,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
return err
}
- log.Debug("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
+ log.Info("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
cron.Start()
// Graceful shut-down on SIGINT/SIGTERM
@@ -172,7 +173,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification()
- updateParams := actions.UpdateParams{
+ updateParams := t.UpdateParams{
Filter: filter,
Cleanup: cleanup,
NoRestart: noRestart,
diff --git a/Dockerfile b/dockerfiles/Dockerfile
similarity index 85%
rename from Dockerfile
rename to dockerfiles/Dockerfile
index 75f70f2..f792d32 100644
--- a/Dockerfile
+++ b/dockerfiles/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.9 as alpine
+FROM alpine:3.11 as alpine
RUN apk add --no-cache \
ca-certificates \
@@ -15,4 +15,4 @@ COPY --from=alpine \
/usr/share/zoneinfo
COPY watchtower /
-ENTRYPOINT ["/watchtower"]
\ No newline at end of file
+ENTRYPOINT ["/watchtower"]
diff --git a/dockerfiles/Dockerfile.dev-self-contained b/dockerfiles/Dockerfile.dev-self-contained
new file mode 100644
index 0000000..307ffbe
--- /dev/null
+++ b/dockerfiles/Dockerfile.dev-self-contained
@@ -0,0 +1,38 @@
+#
+# Builder
+#
+
+FROM golang:alpine as builder
+
+# use version (for example "v0.3.3") or "master"
+ARG WATCHTOWER_VERSION=master
+
+RUN apk add --no-cache \
+ alpine-sdk \
+ ca-certificates \
+ git \
+ tzdata
+
+COPY . /watchtower
+
+RUN \
+ cd /watchtower && \
+ \
+ GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' . && \
+ GO111MODULE=on go test ./... -v
+
+
+#
+# watchtower
+#
+
+FROM scratch
+
+LABEL "com.centurylinklabs.watchtower"="true"
+
+# copy files from other container
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=builder /watchtower/watchtower /watchtower
+
+ENTRYPOINT ["/watchtower"]
diff --git a/Dockerfile.self-contained b/dockerfiles/Dockerfile.self-contained
similarity index 100%
rename from Dockerfile.self-contained
rename to dockerfiles/Dockerfile.self-contained
diff --git a/docs/credential-helpers.md b/docs/credential-helpers.md
deleted file mode 100644
index 1722906..0000000
--- a/docs/credential-helpers.md
+++ /dev/null
@@ -1,64 +0,0 @@
-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
diff --git a/docs/private-registries.md b/docs/private-registries.md
new file mode 100644
index 0000000..13e7618
--- /dev/null
+++ b/docs/private-registries.md
@@ -0,0 +1,118 @@
+Watchtower supports private Docker image registries. In many cases, accessing a private registry requires a valid username and password (i.e., _credentials_). In order to operate in such an environment, watchtower needs to know the credentials to access the registry.
+
+The credentials can be provided to watchtower in a configuration file called `config.json`. There are two ways to generate this configuration file:
+
+* The configuration file can be created manually.
+* Call `docker login ` and share the resulting configuration file.
+
+### Create the configuration file manually
+Create a new configuration file with the following syntax and a base64 encoded username and password `auth` string:
+```json
+{
+ "auths": {
+ "": {
+ "auth": "XXXXXXX"
+ }
+ }
+}
+```
+
+`` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
+
+The required `auth` string can be generated as follows:
+```bash
+echo -n 'username:password' | base64
+```
+
+When the watchtower Docker container is started, the created configuration file (`/config.json` in this example) needs to be passed to the container:
+```bash
+docker run [...] -v /config.json:/config.json containrrr/watchtower
+```
+
+### Share the Docker configuration file
+To pull an image from a private registry, `docker login` needs to be called first, to get access to the registry. The provided credentials are stored in a configuration file called `/.docker/config.json`. This configuration file can be directly used by watchtower. In this case, the creation of an additional configuration file is not necessary.
+
+When the Docker container is started, pass the configuration file to watchtower:
+```bash
+docker run [...] -v /.docker/config.json:/config.json containrrr/watchtower
+```
+
+When creating the watchtower container via docker-compose, use the following lines:
+```yaml
+version: "3"
+[...]
+watchtower:
+ image: index.docker.io/containrrr/watchtower:latest
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /.docker/config.json:/config.json
+[...]
+```
+
+## Credential helpers
+Some private Docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
+To be able to use this together with watchtower, we need to use a credential helper.
+
+To keep the image size small we've decided to not include any helpers in the watchtower image, instead we'll put the
+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.json`:
+```json
+ {
+ "HttpHeaders" : {
+ "User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
+ },
+ "credsStore" : "osxkeychain",
+ "auths" : {
+ "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : {},
+ "https://index.docker.io/v1/": {}
+ },
+ "credHelpers": {
+ "xyzxyzxyz.dkr.ecr.eu-north-1.amazonaws.com" : "ecr-login",
+ "index.docker.io": "osxkeychain"
+ }
+ }
+```
+
+*Note:* `osxkeychain` can be changed to your prefered credentials helper.
diff --git a/go.mod b/go.mod
index b5be153..2d2ced6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,63 +3,57 @@ module github.com/containrrr/watchtower
go 1.12
require (
- github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
- github.com/Microsoft/go-winio v0.4.12
- github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
- github.com/beorn7/perks v1.0.0
- github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111
- github.com/containerd/containerd v1.2.6 // indirect
- github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
- github.com/davecgh/go-spew v1.1.1
+ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
+ github.com/Microsoft/go-winio v0.4.12 // indirect
+ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
+ github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
+ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
+ github.com/bitly/go-simplejson v0.5.0 // indirect
+ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
+ github.com/bugsnag/bugsnag-go v1.5.3 // indirect
+ github.com/bugsnag/panicwrap v1.2.0 // indirect
+ github.com/cenkalti/backoff v2.2.1+incompatible // indirect
+ github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
+ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
- github.com/docker/docker-credential-helpers v0.6.1
- github.com/docker/go v1.5.1-1
- github.com/docker/go-connections v0.4.0
- github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82
- github.com/docker/go-units v0.3.3
- github.com/docker/swarmkit v1.12.0 // indirect
- github.com/gogo/protobuf v1.2.1
- github.com/golang/mock v1.1.1
- github.com/golang/protobuf v1.3.1
- github.com/gorilla/mux v1.7.0
- github.com/hashicorp/go-memdb v1.0.0 // indirect
- github.com/hashicorp/go-version v1.1.0
- github.com/inconshreveable/mousetrap v1.0.0
- github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22
+ github.com/docker/docker-credential-helpers v0.6.1 // indirect
+ github.com/docker/go v1.5.1-1 // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect
+ github.com/docker/go-units v0.3.3 // indirect
+ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
+ github.com/gofrs/uuid v3.2.0+incompatible // indirect
+ github.com/google/certificate-transparency-go v1.0.21 // indirect
+ github.com/gorilla/mux v1.7.0 // indirect
+ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
+ github.com/hashicorp/go-version v1.1.0 // indirect
+ github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/jinzhu/gorm v1.9.11 // indirect
+ github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 // indirect
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
- github.com/konsorten/go-windows-terminal-sequences v1.0.2
- github.com/mattn/go-shellwords v1.0.5 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.1
- github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa
- github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
+ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
+ github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
+ github.com/lib/pq v1.2.0 // indirect
+ github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa // indirect
+ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
- github.com/opencontainers/go-digest v1.0.0-rc1
- github.com/opencontainers/image-spec v1.0.1
+ github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
+ github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1
- github.com/opencontainers/runtime-spec v1.0.1 // indirect
- github.com/opencontainers/selinux v1.2.1 // indirect
- github.com/pkg/errors v0.8.1
- github.com/pmezard/go-difflib v1.0.0
- github.com/prometheus/client_golang v0.9.3
- github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
- github.com/prometheus/common v0.4.0
- github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084
+ github.com/pkg/errors v0.8.1 // indirect
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/sirupsen/logrus v1.4.1
github.com/spf13/cobra v0.0.3
- github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.4.0
- github.com/stretchr/objx v0.1.1
github.com/stretchr/testify v1.3.0
- github.com/theupdateframework/notary v0.6.1
- github.com/vbatts/tar-split v0.11.1 // indirect
- golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692
+ github.com/theupdateframework/notary v0.6.1 // indirect
+ github.com/zmap/zlint v1.0.2 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
- golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
- google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2
- google.golang.org/grpc v1.21.0
+ gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
+ gopkg.in/fatih/pool.v2 v2.0.0 // indirect
+ gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index 2107c46..230df62 100644
--- a/go.sum
+++ b/go.sum
@@ -1,25 +1,46 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
+cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111 h1:gRfsoKtF1tba+hVsNgo7OKG7a35hBK30ouOTHPgqFf8=
-github.com/brysgo/gomock_ginkgo v0.0.0-20180512161304-be2c1b0e4111/go.mod h1:H1ipqq0hhUWJgVeQ5dbUe/C8YptJrE/VGDQp9bI+qTo=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
+github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04=
+github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
+github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
+github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/containerd/containerd v1.2.6 h1:K38ZSAA9oKSrX3iFNY+4SddZ8hH1TCMCerc8NHfcKBQ=
-github.com/containerd/containerd v1.2.6/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 h1:A7RURps5t4yDU0zktlgrE3Bdmjfv35nVs+xJdoWgIgY=
+github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -30,6 +51,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29 h1:ciaXDHaWQda0nvevWqcjtXX/buQY3e0lga1vq8Batq0=
@@ -48,38 +71,56 @@ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zF
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/swarmkit v1.12.0 h1:vcbNXevt9xOod0miQxkp9WZ70IsOCe8geXkmFnXP2e0=
-github.com/docker/swarmkit v1.12.0/go.mod h1:n3Z4lIEl7g261ptkGDBcYi/3qBMDl9csaAhwi2MPejs=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
+github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
+github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIUjTQ=
-github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -89,79 +130,97 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
+github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY=
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa h1:gOXc1BXmFuxWYmTfoK51YJR7srco3CwbsVHgr+8Y4r0=
github.com/miekg/pkcs11 v0.0.0-20190401114359-553cfdd26aaa/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/selinux v1.2.1 h1:Svlc+L67YcjN4K2bqD8Wlw9jtMlmZ+1FEGn6zsm8am0=
-github.com/opencontainers/selinux v1.2.1/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
-github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872 h1:0aNv3xC7DmQoy1/x1sMh18g+fihWW68LL13i8ao9kl4=
-github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@@ -188,34 +247,45 @@ github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
+github.com/weppos/publicsuffix-go v0.4.0 h1:YSnfg3V65LcCFKtIGKGoBhkyKolEd0hlipcXaOjdnQw=
+github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
+github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
+github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
+github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
+github.com/zmap/zlint v1.0.2 h1:07+WuC/prlXVlWa1CJx2lCpuCd8biIeBAVnwTN2CPaA=
+github.com/zmap/zlint v1.0.2/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692 h1:GRhHqDOgeDr6QDTtq9gn2O4iKvm5dsbfqD/TXb0KLX0=
-golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b h1:/zjbcJPEGAyu6Is/VBOALsgdi4z9+kz/Vtdm6S+beD0=
-golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -226,32 +296,48 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2 h1:8FyEBtGg6Px24p+H2AkuVWqhj4+R9fo+fZD17mg+lzk=
-google.golang.org/genproto v0.0.0-20190401181712-f467c93bbac2/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
-google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
+gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
+gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
+gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
+gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -261,4 +347,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/goreleaser.yml b/goreleaser.yml
index 4fa3841..927cdcd 100644
--- a/goreleaser.yml
+++ b/goreleaser.yml
@@ -29,7 +29,7 @@ dockers:
goos: linux
goarch: amd64
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:amd64-{{ .Version }}
- containrrr/watchtower:amd64-latest
@@ -39,7 +39,7 @@ dockers:
goos: linux
goarch: 386
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:i386-{{ .Version }}
- containrrr/watchtower:i386-latest
@@ -49,7 +49,7 @@ dockers:
goos: linux
goarch: arm
goarm: 6
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:armhf-{{ .Version }}
- containrrr/watchtower:armhf-latest
@@ -59,7 +59,7 @@ dockers:
goos: linux
goarch: arm64
goarm: ''
- dockerfile: Dockerfile
+ dockerfile: dockerfiles/Dockerfile
image_templates:
- containrrr/watchtower:arm64v8-{{ .Version }}
- containrrr/watchtower:arm64v8-latest
diff --git a/internal/actions/actions_suite_test.go b/internal/actions/actions_suite_test.go
index 76d2be5..2c9b0c8 100644
--- a/internal/actions/actions_suite_test.go
+++ b/internal/actions/actions_suite_test.go
@@ -1,18 +1,16 @@
package actions_test
import (
- "errors"
"github.com/containrrr/watchtower/internal/actions"
"testing"
"time"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
- "github.com/docker/docker/api/types"
- t "github.com/containrrr/watchtower/pkg/types"
cli "github.com/docker/docker/client"
+ . "github.com/containrrr/watchtower/internal/actions/mocks"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
@@ -24,7 +22,7 @@ func TestActions(t *testing.T) {
var _ = Describe("the actions package", func() {
var dockerClient cli.CommonAPIClient
- var client mockClient
+ var client MockClient
BeforeSuite(func() {
server := mocks.NewMockAPIServer()
dockerClient, _ = cli.NewClientWithOpts(
@@ -32,12 +30,15 @@ var _ = Describe("the actions package", func() {
cli.WithHTTPClient(server.Client()))
})
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{},
- }
+ pullImages := false
+ removeVolumes := false
+
+ client = CreateMockClient(
+ &TestData {},
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
Describe("the check prerequisites method", func() {
@@ -51,7 +52,7 @@ var _ = Describe("the actions package", func() {
When("given an array of one", func() {
It("should not do anything", func() {
client.TestData.Containers = []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container",
"test-container",
"watchtower",
@@ -63,27 +64,30 @@ var _ = Describe("the actions package", func() {
})
When("given multiple containers", func() {
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{
+ pullImages := false
+ removeVolumes := false
+ client = CreateMockClient(
+ &TestData{
NameOfContainerToKeep: "test-container-02",
Containers: []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
- createMockContainer(
+ CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
},
},
- }
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
+
It("should stop all but the latest one", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
@@ -91,96 +95,40 @@ var _ = Describe("the actions package", func() {
})
When("deciding whether to cleanup images", func() {
BeforeEach(func() {
- client = mockClient{
- api: dockerClient,
- pullImages: false,
- removeVolumes: false,
- TestData: &TestData{
+ pullImages := false
+ removeVolumes := false
+
+ client = CreateMockClient(
+ &TestData{
Containers: []container.Container{
- createMockContainer(
+ CreateMockContainer(
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
- createMockContainer(
+ CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
},
},
- }
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
})
It("should try to delete the image if the cleanup flag is true", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, true)
Expect(err).NotTo(HaveOccurred())
- Expect(client.TestData.TriedToRemoveImage).To(BeTrue())
+ Expect(client.TestData.TriedToRemoveImage()).To(BeTrue())
})
It("should not try to delete the image if the cleanup flag is false", func() {
err := actions.CheckForMultipleWatchtowerInstances(client, false)
Expect(err).NotTo(HaveOccurred())
- Expect(client.TestData.TriedToRemoveImage).To(BeFalse())
+ Expect(client.TestData.TriedToRemoveImage()).To(BeFalse())
})
})
})
})
-func createMockContainer(id string, name string, image string, created time.Time) container.Container {
- content := types.ContainerJSON{
- ContainerJSONBase: &types.ContainerJSONBase{
- ID: id,
- Image: image,
- Name: name,
- Created: created.String(),
- },
- }
- return *container.NewContainer(&content, nil)
-}
-
-type mockClient struct {
- TestData *TestData
- api cli.CommonAPIClient
- pullImages bool
- removeVolumes bool
-}
-
-type TestData struct {
- TriedToRemoveImage bool
- NameOfContainerToKeep string
- Containers []container.Container
-}
-
-func (client mockClient) ListContainers(f t.Filter) ([]container.Container, error) {
- return client.TestData.Containers, nil
-}
-
-func (client mockClient) StopContainer(c container.Container, d time.Duration) error {
- if c.Name() == client.TestData.NameOfContainerToKeep {
- return errors.New("tried to stop the instance we want to keep")
- }
- return nil
-}
-func (client mockClient) StartContainer(c container.Container) (string, error) {
- panic("Not implemented")
-}
-
-func (client mockClient) RenameContainer(c container.Container, s string) error {
- panic("Not implemented")
-}
-
-func (client mockClient) RemoveImage(c container.Container) error {
- client.TestData.TriedToRemoveImage = true
- return nil
-}
-
-func (client mockClient) GetContainer(containerID string) (container.Container, error) {
- return container.Container{}, nil
-}
-
-func (client mockClient) ExecuteCommand(containerID string, command string) error {
- return nil
-}
-
-func (client mockClient) IsContainerStale(c container.Container) (bool, error) {
- panic("Not implemented")
-}
diff --git a/internal/actions/check.go b/internal/actions/check.go
index 8574300..785701f 100644
--- a/internal/actions/check.go
+++ b/internal/actions/check.go
@@ -3,6 +3,8 @@ package actions
import (
"errors"
"fmt"
+ "github.com/containrrr/watchtower/pkg/filters"
+ "github.com/containrrr/watchtower/pkg/sorter"
"sort"
"strings"
"time"
@@ -19,7 +21,7 @@ import (
// will stop and remove all but the most recently started container.
func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool) error {
awaitDockerClient()
- containers, err := client.ListContainers(container.WatchtowerContainersFilter)
+ containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
if err != nil {
log.Fatal(err)
@@ -39,7 +41,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
var cleanupErrors int
var stopErrors int
- sort.Sort(container.ByCreated(containers))
+ sort.Sort(sorter.ByCreated(containers))
allContainersExceptLast := containers[0 : len(containers)-1]
for _, c := range allContainersExceptLast {
@@ -51,7 +53,7 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
}
if cleanup {
- if err := client.RemoveImage(c); err != nil {
+ if err := client.RemoveImageByID(c.ImageID()); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
cleanupErrors++
diff --git a/internal/actions/mocks/client.go b/internal/actions/mocks/client.go
new file mode 100644
index 0000000..dad2506
--- /dev/null
+++ b/internal/actions/mocks/client.go
@@ -0,0 +1,84 @@
+package mocks
+
+import (
+ "errors"
+ "github.com/containrrr/watchtower/pkg/container"
+ "time"
+
+ t "github.com/containrrr/watchtower/pkg/types"
+ cli "github.com/docker/docker/client"
+)
+
+// MockClient is a mock that passes as a watchtower Client
+type MockClient struct {
+ TestData *TestData
+ api cli.CommonAPIClient
+ pullImages bool
+ removeVolumes bool
+}
+
+// TestData is the data used to perform the test
+type TestData struct {
+ TriedToRemoveImageCount int
+ NameOfContainerToKeep string
+ Containers []container.Container
+}
+
+// TriedToRemoveImage is a test helper function to check whether RemoveImageByID has been called
+func (testdata *TestData) TriedToRemoveImage() bool {
+ return testdata.TriedToRemoveImageCount > 0
+}
+
+// CreateMockClient creates a mock watchtower Client for usage in tests
+func CreateMockClient(data *TestData, api cli.CommonAPIClient, pullImages bool, removeVolumes bool) MockClient {
+ return MockClient {
+ data,
+ api,
+ pullImages,
+ removeVolumes,
+ }
+}
+
+// ListContainers is a mock method returning the provided container testdata
+func (client MockClient) ListContainers(f t.Filter) ([]container.Container, error) {
+ return client.TestData.Containers, nil
+}
+
+// StopContainer is a mock method
+func (client MockClient) StopContainer(c container.Container, d time.Duration) error {
+ if c.Name() == client.TestData.NameOfContainerToKeep {
+ return errors.New("tried to stop the instance we want to keep")
+ }
+ return nil
+}
+
+// StartContainer is a mock method
+func (client MockClient) StartContainer(c container.Container) (string, error) {
+ return "", nil
+}
+// RenameContainer is a mock method
+func (client MockClient) RenameContainer(c container.Container, s string) error {
+ return nil
+}
+
+// RemoveImageByID increments the TriedToRemoveImageCount on being called
+func (client MockClient) RemoveImageByID(id string) error {
+ client.TestData.TriedToRemoveImageCount++
+ return nil
+}
+
+// GetContainer is a mock method
+func (client MockClient) GetContainer(containerID string) (container.Container, error) {
+ return container.Container{}, nil
+}
+
+// ExecuteCommand is a mock method
+func (client MockClient) ExecuteCommand(containerID string, command string) error {
+ return nil
+}
+
+// IsContainerStale is always true for the mock client
+func (client MockClient) IsContainerStale(c container.Container) (bool, error) {
+ return true, nil
+}
+
diff --git a/internal/actions/mocks/container.go b/internal/actions/mocks/container.go
new file mode 100644
index 0000000..060340e
--- /dev/null
+++ b/internal/actions/mocks/container.go
@@ -0,0 +1,29 @@
+package mocks
+
+import (
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/docker/docker/api/types"
+ container2 "github.com/docker/docker/api/types/container"
+ "time"
+)
+
+// CreateMockContainer creates a container substitute valid for testing
+func CreateMockContainer(id string, name string, image string, created time.Time) container.Container {
+ content := types.ContainerJSON{
+ ContainerJSONBase: &types.ContainerJSONBase{
+ ID: id,
+ Image: image,
+ Name: name,
+ Created: created.String(),
+ },
+ Config: &container2.Config{
+ Labels: make(map[string]string),
+ },
+ }
+ return *container.NewContainer(
+ &content,
+ &types.ImageInspect{
+ ID: image,
+ },
+ )
+}
diff --git a/internal/actions/update.go b/internal/actions/update.go
index 02013d6..874e705 100644
--- a/internal/actions/update.go
+++ b/internal/actions/update.go
@@ -3,6 +3,9 @@ package actions
import (
"github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/lifecycle"
+ "github.com/containrrr/watchtower/pkg/sorter"
+ "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)
@@ -10,10 +13,12 @@ import (
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
-func Update(client container.Client, params UpdateParams) error {
+func Update(client container.Client, params types.UpdateParams) error {
log.Debug("Checking containers for updated images")
- executePreCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePreChecks(client, params)
+ }
containers, err := client.ListContainers(params.Filter)
if err != nil {
@@ -30,7 +35,7 @@ func Update(client container.Client, params UpdateParams) error {
containers[i].Stale = stale
}
- containers, err = container.SortByDependencies(containers)
+ containers, err = sorter.SortByDependencies(containers)
if err != nil {
return err
}
@@ -38,24 +43,28 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers)
if params.MonitorOnly {
- executePostCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePostChecks(client, params)
+ }
return nil
}
stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params)
- executePostCheck(client, params)
+ if params.LifecycleHooks {
+ lifecycle.ExecutePostChecks(client, params)
+ }
return nil
}
-func stopContainersInReversedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
for i := len(containers) - 1; i >= 0; i-- {
stopStaleContainer(containers[i], client, params)
}
}
-func stopStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+func stopStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
if container.IsWatchtower() {
log.Debugf("This is the watchtower container %s", container.Name())
return
@@ -64,24 +73,36 @@ func stopStaleContainer(container container.Container, client container.Client,
if !container.Stale {
return
}
+ if params.LifecycleHooks {
+ lifecycle.ExecutePreUpdateCommand(client, container)
- executePreUpdateCommand(client, container)
+ }
if err := client.StopContainer(container, params.Timeout); err != nil {
log.Error(err)
}
}
-func restartContainersInSortedOrder(containers []container.Container, client container.Client, params UpdateParams) {
+func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
+ imageIDs := make(map[string]bool)
+
for _, container := range containers {
if !container.Stale {
continue
}
restartStaleContainer(container, client, params)
+ imageIDs[container.ImageID()] = true
+ }
+ if params.Cleanup {
+ for imageID := range imageIDs {
+ if err := client.RemoveImageByID(imageID); err != nil {
+ log.Error(err)
+ }
+ }
}
}
-func restartStaleContainer(container container.Container, client container.Client, params UpdateParams) {
+func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
// Since we can't shutdown a watchtower container immediately, we need to
// start the new one while the old one is still running. This prevents us
// from re-using the same container name so we first rename the current
@@ -97,13 +118,7 @@ func restartStaleContainer(container container.Container, client container.Clien
if newContainerID, err := client.StartContainer(container); err != nil {
log.Error(err)
} else if container.Stale && params.LifecycleHooks {
- executePostUpdateCommand(client, newContainerID)
- }
- }
-
- if params.Cleanup {
- if err := client.RemoveImage(container); err != nil {
- log.Error(err)
+ lifecycle.ExecutePostUpdateCommand(client, newContainerID)
}
}
}
@@ -126,82 +141,3 @@ func checkDependencies(containers []container.Container) {
}
}
}
-
-func executePreCheck(client container.Client, params UpdateParams) {
- containers, err := client.ListContainers(params.Filter)
- if err != nil {
- return
- }
- for _, container := range containers {
- executePreCheckCommand(client, container)
- }
-}
-
-func executePostCheck(client container.Client, params UpdateParams) {
- containers, err := client.ListContainers(params.Filter)
- if err != nil {
- return
- }
- for _, container := range containers {
- executePostCheckCommand(client, container)
- }
-}
-
-func executePreCheckCommand(client container.Client, container container.Container) {
- command := container.GetLifecyclePreCheckCommand()
- if len(command) == 0 {
- log.Debug("No pre-check command supplied. Skipping")
- return
- }
-
- log.Info("Executing pre-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePostCheckCommand(client container.Client, container container.Container) {
- command := container.GetLifecyclePostCheckCommand()
- if len(command) == 0 {
- log.Debug("No post-check command supplied. Skipping")
- return
- }
-
- log.Info("Executing post-check command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePreUpdateCommand(client container.Client, container container.Container) {
-
- command := container.GetLifecyclePreUpdateCommand()
- if len(command) == 0 {
- log.Debug("No pre-update command supplied. Skipping")
- return
- }
-
- log.Info("Executing pre-update command.")
- if err := client.ExecuteCommand(container.ID(), command); err != nil {
- log.Error(err)
- }
-}
-
-func executePostUpdateCommand(client container.Client, newContainerID string) {
- newContainer, err := client.GetContainer(newContainerID)
- if err != nil {
- log.Error(err)
- return
- }
-
- command := newContainer.GetLifecyclePostUpdateCommand()
- if len(command) == 0 {
- log.Debug("No post-update command supplied. Skipping")
- return
- }
-
- log.Info("Executing post-update command.")
- if err := client.ExecuteCommand(newContainerID, command); err != nil {
- log.Error(err)
- }
-}
diff --git a/internal/actions/update_test.go b/internal/actions/update_test.go
new file mode 100644
index 0000000..3c9befe
--- /dev/null
+++ b/internal/actions/update_test.go
@@ -0,0 +1,85 @@
+package actions_test
+
+import (
+ "github.com/containrrr/watchtower/internal/actions"
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/container/mocks"
+ "github.com/containrrr/watchtower/pkg/types"
+ cli "github.com/docker/docker/client"
+ "time"
+
+ . "github.com/containrrr/watchtower/internal/actions/mocks"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+
+var _ = Describe("the update action", func() {
+ var dockerClient cli.CommonAPIClient
+ var client MockClient
+
+ BeforeEach(func() {
+ server := mocks.NewMockAPIServer()
+ dockerClient, _ = cli.NewClientWithOpts(
+ cli.WithHost(server.URL),
+ cli.WithHTTPClient(server.Client()))
+ })
+
+
+ When("watchtower has been instructed to clean up", func() {
+ BeforeEach(func() {
+ pullImages := false
+ removeVolumes := false
+ client = CreateMockClient(
+ &TestData{
+ NameOfContainerToKeep: "test-container-02",
+ Containers: []container.Container{
+ CreateMockContainer(
+ "test-container-01",
+ "test-container-01",
+ "fake-image:latest",
+ time.Now().AddDate(0, 0, -1)),
+ CreateMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "fake-image:latest",
+ time.Now()),
+ CreateMockContainer(
+ "test-container-02",
+ "test-container-02",
+ "fake-image:latest",
+ time.Now()),
+ },
+ },
+ dockerClient,
+ pullImages,
+ removeVolumes,
+ )
+ })
+
+ When("there are multiple containers using the same image", func() {
+ It("should only try to remove the image once", func() {
+
+ err := actions.Update(client, types.UpdateParams{ Cleanup: true })
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
+ })
+ })
+ When("there are multiple containers using different images", func() {
+ It("should try to remove each of them", func() {
+ client.TestData.Containers = append(
+ client.TestData.Containers,
+ CreateMockContainer(
+ "unique-test-container",
+ "unique-test-container",
+ "unique-fake-image:latest",
+ time.Now(),
+ ),
+ )
+ err := actions.Update(client, types.UpdateParams{ Cleanup: true })
+ Expect(err).NotTo(HaveOccurred())
+ Expect(client.TestData.TriedToRemoveImageCount).To(Equal(2))
+ })
+ })
+ })
+})
diff --git a/mkdocs.yml b/mkdocs.yml
index e5e7c34..645c1cc 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,7 +14,7 @@ nav:
- 'Arguments': 'arguments.md'
- 'Notifications': 'notifications.md'
- 'Container selection': 'container-selection.md'
- - 'Credential helpers': 'credential-helpers.md'
+ - 'Private registries': 'private-registries.md'
- 'Linked containers': 'linked-containers.md'
- 'Remote hosts': 'remote-hosts.md'
- 'Secure connections': 'secure-connections.md'
diff --git a/pkg/container/client.go b/pkg/container/client.go
index 5877eb4..607b84c 100644
--- a/pkg/container/client.go
+++ b/pkg/container/client.go
@@ -3,6 +3,7 @@ package container
import (
"bytes"
"fmt"
+ "github.com/containrrr/watchtower/pkg/registry"
"io/ioutil"
"strings"
"time"
@@ -12,7 +13,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
- dockerclient "github.com/docker/docker/client"
+ sdkClient "github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@@ -29,7 +30,8 @@ type Client interface {
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
ExecuteCommand(containerID string, command string) error
- RemoveImage(Container) error
+ RemoveImageByID(string) error
+
}
// NewClient returns a new Client instance which can be used to interact with
@@ -39,7 +41,7 @@ type Client interface {
// * DOCKER_TLS_VERIFY whether to verify tls certificates
// * DOCKER_API_VERSION the minimum docker api version to work with
func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool) Client {
- cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv)
+ cli, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv)
if err != nil {
log.Fatalf("Error instantiating Docker client: %s", err)
@@ -55,7 +57,7 @@ func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeV
}
type dockerClient struct {
- api dockerclient.CommonAPIClient
+ api sdkClient.CommonAPIClient
pullImages bool
removeVolumes bool
includeStopped bool
@@ -156,7 +158,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
// Wait for container to be removed. In this case an error is a good thing
if err := client.waitForStopOrTimeout(c, timeout); err == nil {
- return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
+ return fmt.Errorf("container %s (%s) could not be removed", c.Name(), c.ID())
}
return nil
@@ -230,59 +232,72 @@ func (client dockerClient) RenameContainer(c Container, newName string) error {
return client.api.ContainerRename(bg, c.ID(), newName)
}
-func (client dockerClient) IsContainerStale(c Container) (bool, error) {
- bg := context.Background()
- oldImageInfo := c.imageInfo
- imageName := c.ImageName()
+func (client dockerClient) IsContainerStale(container Container) (bool, error) {
+ ctx := context.Background()
- if client.pullImages {
- log.Debugf("Pulling %s for %s", imageName, c.Name())
+ if !client.pullImages {
+ log.Debugf("Skipping image pull.")
+ } else if err := client.PullImage(ctx, container); err != nil {
+ return false, err
+ }
- var opts types.ImagePullOptions // ImagePullOptions can take a RegistryAuth arg to authenticate against a private registry
- auth, err := EncodedAuth(imageName)
- log.Debugf("Got auth value: %s", auth)
- log.Debugf("Got image name: %s", imageName)
- if err != nil {
- log.Debugf("Error loading authentication credentials %s", err)
- return false, err
- } else if auth == "" {
- log.Debugf("No authentication credentials found for %s", imageName)
- opts = types.ImagePullOptions{} // empty/no auth credentials
- } else {
- opts = types.ImagePullOptions{RegistryAuth: auth, PrivilegeFunc: DefaultAuthHandler}
- }
+ return client.HasNewImage(ctx, container)
+}
- response, err := client.api.ImagePull(bg, imageName, opts)
- if err != nil {
- log.Debugf("Error pulling image %s, %s", imageName, err)
- return false, err
- }
- defer response.Close()
+func (client dockerClient) HasNewImage(ctx context.Context, container Container) (bool, error) {
+ oldImageID := container.imageInfo.ID
+ imageName := container.ImageName()
- // 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(ctx, imageName)
+ if err != nil {
+ return false, err
}
- newImageInfo, _, err := client.api.ImageInspectWithRaw(bg, imageName)
+ if newImageInfo.ID == oldImageID {
+ log.Debugf("No new images found for %s", container.Name())
+ return false, nil
+ }
+
+ log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
+ return true, nil
+}
+
+func (client dockerClient) PullImage(ctx context.Context, container Container) error {
+ containerName := container.Name()
+ imageName := container.ImageName()
+ log.Debugf("Pulling %s for %s", imageName, containerName)
+
+ opts, err := registry.GetPullOptions(imageName)
if err != nil {
- return false, err
+ log.Debugf("Error loading authentication credentials %s", err)
+ return err
}
- if newImageInfo.ID != oldImageInfo.ID {
- log.Infof("Found new %s image (%s)", imageName, newImageInfo.ID)
- return true, nil
+ response, err := client.api.ImagePull(ctx, imageName, opts)
+ if err != nil {
+ log.Debugf("Error pulling image %s, %s", imageName, err)
+ return err
}
- log.Debugf("No new images found for %s", c.Name())
- return false, nil
+ defer response.Close()
+ // the pull request will be aborted prematurely unless the response is read
+ if _, err = ioutil.ReadAll(response); err != nil {
+ log.Error(err)
+ return err
+ }
+ return nil
}
-func (client dockerClient) RemoveImage(c Container) error {
- imageID := c.ImageID()
- log.Infof("Removing image %s", imageID)
- _, err := client.api.ImageRemove(context.Background(), imageID, types.ImageRemoveOptions{Force: true})
+func (client dockerClient) RemoveImageByID(id string) error {
+ log.Infof("Removing image %s", id)
+
+ _, err := client.api.ImageRemove(
+ context.Background(),
+ id,
+ types.ImageRemoveOptions{
+ Force: true,
+ })
+
return err
}
diff --git a/pkg/container/container.go b/pkg/container/container.go
index a4db4cf..f88ff91 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -28,6 +28,11 @@ type Container struct {
imageInfo *types.ImageInspect
}
+// ContainerInfo fetches JSON info for the container
+func (c Container) ContainerInfo() *types.ContainerJSON {
+ return c.containerInfo
+}
+
// ID returns the Docker container ID.
func (c Container) ID() string {
return c.containerInfo.ID
@@ -133,6 +138,7 @@ func (c Container) StopSignal() string {
// the options overridden at runtime.
func (c Container) runtimeConfig() *dockercontainer.Config {
config := c.containerInfo.Config
+ hostConfig := c.containerInfo.HostConfig
imageConfig := c.imageInfo.Config
if config.WorkingDir == imageConfig.WorkingDir {
@@ -143,6 +149,10 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
config.User = ""
}
+ if hostConfig.NetworkMode.IsContainer() {
+ config.Hostname = ""
+ }
+
if util.SliceEqual(config.Entrypoint, imageConfig.Entrypoint) {
config.Entrypoint = nil
if util.SliceEqual(config.Cmd, imageConfig.Cmd) {
diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go
index 9e1b213..fe838f7 100644
--- a/pkg/container/container_test.go
+++ b/pkg/container/container_test.go
@@ -2,6 +2,7 @@ package container
import (
"github.com/containrrr/watchtower/pkg/container/mocks"
+ "github.com/containrrr/watchtower/pkg/filters"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
cli "github.com/docker/docker/client"
@@ -34,14 +35,14 @@ var _ = Describe("the container", func() {
})
When("listing containers without any filter", func() {
It("should return all available containers", func() {
- containers, err := client.ListContainers(noFilter)
+ containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 2).To(BeTrue())
})
})
When("listing containers with a filter matching nothing", func() {
It("should return an empty array", func() {
- filter := filterByNames([]string{"lollercoaster"}, noFilter)
+ filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
containers, err := client.ListContainers(filter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 0).To(BeTrue())
@@ -49,7 +50,7 @@ var _ = Describe("the container", func() {
})
When("listing containers with a watchtower filter", func() {
It("should return only the watchtower container", func() {
- containers, err := client.ListContainers(WatchtowerContainersFilter)
+ containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 1).To(BeTrue())
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
@@ -62,7 +63,7 @@ var _ = Describe("the container", func() {
pullImages: false,
includeStopped: true,
}
- containers, err := client.ListContainers(noFilter)
+ containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred())
Expect(len(containers) > 0).To(BeTrue())
})
diff --git a/pkg/container/filters.go b/pkg/filters/filters.go
similarity index 63%
rename from pkg/container/filters.go
rename to pkg/filters/filters.go
index b4d4911..b923745 100644
--- a/pkg/container/filters.go
+++ b/pkg/filters/filters.go
@@ -1,15 +1,15 @@
-package container
+package filters
import t "github.com/containrrr/watchtower/pkg/types"
// WatchtowerContainersFilter filters only watchtower containers
func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatchtower() }
-// Filter no containers and returns all
-func noFilter(t.FilterableContainer) bool { return true }
+// NoFilter will not filter out any containers
+func NoFilter(t.FilterableContainer) bool { return true }
-// Filters containers which don't have a specified name
-func filterByNames(names []string, baseFilter t.Filter) t.Filter {
+// FilterByNames returns all containers that match the specified name
+func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 {
return baseFilter
}
@@ -24,8 +24,8 @@ func filterByNames(names []string, baseFilter t.Filter) t.Filter {
}
}
-// Filters out containers that don't have the 'enableLabel'
-func filterByEnableLabel(baseFilter t.Filter) t.Filter {
+// FilterByEnableLabel returns all containers that have the enabled label set
+func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool {
// If label filtering is enabled, containers should only be considered
// if the label is specifically set.
@@ -38,8 +38,8 @@ func filterByEnableLabel(baseFilter t.Filter) t.Filter {
}
}
-// Filters out containers that have a 'enableLabel' and is set to disable.
-func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
+// FilterByDisabledLabel returns all containers that have the enabled label set to disable
+func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool {
enabledLabel, ok := c.Enabled()
if ok && !enabledLabel {
@@ -53,13 +53,13 @@ func filterByDisabledLabel(baseFilter t.Filter) t.Filter {
// BuildFilter creates the needed filter of containers
func BuildFilter(names []string, enableLabel bool) t.Filter {
- filter := noFilter
- filter = filterByNames(names, filter)
+ filter := NoFilter
+ filter = FilterByNames(names, filter)
if enableLabel {
// If label filtering is enabled, containers should only be considered
// if the label is specifically set.
- filter = filterByEnableLabel(filter)
+ filter = FilterByEnableLabel(filter)
}
- filter = filterByDisabledLabel(filter)
+ filter = FilterByDisabledLabel(filter)
return filter
}
diff --git a/pkg/container/filters_test.go b/pkg/filters/filters_test.go
similarity index 94%
rename from pkg/container/filters_test.go
rename to pkg/filters/filters_test.go
index 4118335..d24b186 100644
--- a/pkg/container/filters_test.go
+++ b/pkg/filters/filters_test.go
@@ -1,4 +1,4 @@
-package container
+package filters
import (
"testing"
@@ -20,7 +20,7 @@ func TestWatchtowerContainersFilter(t *testing.T) {
func TestNoFilter(t *testing.T) {
container := new(mocks.FilterableContainer)
- assert.True(t, noFilter(container))
+ assert.True(t, NoFilter(container))
container.AssertExpectations(t)
}
@@ -28,12 +28,12 @@ func TestNoFilter(t *testing.T) {
func TestFilterByNames(t *testing.T) {
var names []string
- filter := filterByNames(names, nil)
+ filter := FilterByNames(names, nil)
assert.Nil(t, filter)
names = append(names, "test")
- filter = filterByNames(names, noFilter)
+ filter = FilterByNames(names, NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@@ -48,7 +48,7 @@ func TestFilterByNames(t *testing.T) {
}
func TestFilterByEnableLabel(t *testing.T) {
- filter := filterByEnableLabel(noFilter)
+ filter := FilterByEnableLabel(NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
@@ -68,7 +68,7 @@ func TestFilterByEnableLabel(t *testing.T) {
}
func TestFilterByDisabledLabel(t *testing.T) {
- filter := filterByDisabledLabel(noFilter)
+ filter := FilterByDisabledLabel(NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go
new file mode 100644
index 0000000..9823f9d
--- /dev/null
+++ b/pkg/lifecycle/lifecycle.go
@@ -0,0 +1,93 @@
+package lifecycle
+
+import (
+ "github.com/containrrr/watchtower/pkg/container"
+ "github.com/containrrr/watchtower/pkg/types"
+ log "github.com/sirupsen/logrus"
+)
+
+// ExecutePreChecks tries to run the pre-check lifecycle hook for all containers included by the current filter.
+func ExecutePreChecks(client container.Client, params types.UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ ExecutePreCheckCommand(client, container)
+ }
+}
+
+// ExecutePostChecks tries to run the post-check lifecycle hook for all containers included by the current filter.
+func ExecutePostChecks(client container.Client, params types.UpdateParams) {
+ containers, err := client.ListContainers(params.Filter)
+ if err != nil {
+ return
+ }
+ for _, container := range containers {
+ ExecutePostCheckCommand(client, container)
+ }
+}
+
+// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
+func ExecutePreCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePreCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing pre-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
+func ExecutePostCheckCommand(client container.Client, container container.Container) {
+ command := container.GetLifecyclePostCheckCommand()
+ if len(command) == 0 {
+ log.Debug("No post-check command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing post-check command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
+func ExecutePreUpdateCommand(client container.Client, container container.Container) {
+
+ command := container.GetLifecyclePreUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No pre-update command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing pre-update command.")
+ if err := client.ExecuteCommand(container.ID(), command); err != nil {
+ log.Error(err)
+ }
+}
+
+// ExecutePostUpdateCommand tries to run the post-update lifecycle hook for a single container.
+func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
+ newContainer, err := client.GetContainer(newContainerID)
+ if err != nil {
+ log.Error(err)
+ return
+ }
+
+ command := newContainer.GetLifecyclePostUpdateCommand()
+ if len(command) == 0 {
+ log.Debug("No post-update command supplied. Skipping")
+ return
+ }
+
+ log.Info("Executing post-update command.")
+ if err := client.ExecuteCommand(newContainerID, command); err != nil {
+ log.Error(err)
+ }
+}
+
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index ca54499..fe4ca03 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"
"net/smtp"
"os"
+ "strings"
"time"
t "github.com/containrrr/watchtower/pkg/types"
@@ -113,7 +114,7 @@ func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
if e.User != "" {
auth = smtp.PlainAuth("", e.User, e.Password, e.Server)
}
- err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, []string{e.To}, msg)
+ err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, strings.Split(e.To, ","), msg)
if err != nil {
// Use fmt so it doesn't trigger another email.
fmt.Println("Failed to send notification email: ", err)
diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go
new file mode 100644
index 0000000..2d7b9a8
--- /dev/null
+++ b/pkg/registry/registry.go
@@ -0,0 +1,33 @@
+package registry
+
+import (
+ "github.com/docker/docker/api/types"
+ log "github.com/sirupsen/logrus"
+)
+
+// GetPullOptions creates a struct with all options needed for pulling images from a registry
+func GetPullOptions(imageName string) (types.ImagePullOptions, error) {
+ auth, err := EncodedAuth(imageName)
+ log.Debugf("Got image name: %s", imageName)
+ if err != nil {
+ return types.ImagePullOptions{}, err
+ }
+
+ log.Debugf("Got auth value: %s", auth)
+ if auth == "" {
+ return types.ImagePullOptions{}, nil
+ }
+
+ return types.ImagePullOptions{
+ RegistryAuth: auth,
+ PrivilegeFunc: DefaultAuthHandler,
+ }, nil
+}
+
+// DefaultAuthHandler will be invoked if an AuthConfig is rejected
+// It could be used to return a new value for the "X-Registry-Auth" authentication header,
+// but there's no point trying again with the same value as used in AuthConfig
+func DefaultAuthHandler() (string, error) {
+ log.Debug("Authentication request was rejected. Trying again without authentication")
+ return "", nil
+}
diff --git a/pkg/container/trust.go b/pkg/registry/trust.go
similarity index 88%
rename from pkg/container/trust.go
rename to pkg/registry/trust.go
index 63b76a6..7403d46 100644
--- a/pkg/container/trust.go
+++ b/pkg/registry/trust.go
@@ -1,4 +1,4 @@
-package container
+package registry
import (
"errors"
@@ -97,11 +97,3 @@ func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
func EncodeAuth(auth types.AuthConfig) (string, error) {
return command.EncodeAuthToBase64(auth)
}
-
-// DefaultAuthHandler will be invoked if an AuthConfig is rejected
-// It could be used to return a new value for the "X-Registry-Auth" authentication header,
-// but there's no point trying again with the same value as used in AuthConfig
-func DefaultAuthHandler() (string, error) {
- log.Debug("Authentication request was rejected. Trying again without authentication")
- return "", nil
-}
diff --git a/pkg/container/trust_test.go b/pkg/registry/trust_test.go
similarity index 99%
rename from pkg/container/trust_test.go
rename to pkg/registry/trust_test.go
index 7d2ac96..8ffe1b9 100644
--- a/pkg/container/trust_test.go
+++ b/pkg/registry/trust_test.go
@@ -1,4 +1,4 @@
-package container
+package registry
import (
"github.com/stretchr/testify/assert"
diff --git a/pkg/container/sort.go b/pkg/sorter/sort.go
similarity index 71%
rename from pkg/container/sort.go
rename to pkg/sorter/sort.go
index 391a8b6..1e27f1b 100644
--- a/pkg/container/sort.go
+++ b/pkg/sorter/sort.go
@@ -1,13 +1,14 @@
-package container
+package sorter
import (
"fmt"
+ "github.com/containrrr/watchtower/pkg/container"
"time"
)
// ByCreated allows a list of Container structs to be sorted by the container's
// created date.
-type ByCreated []Container
+type ByCreated []container.Container
func (c ByCreated) Len() int { return len(c) }
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
@@ -15,12 +16,12 @@ func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
// Less will compare two elements (identified by index) in the Container
// list by created-date.
func (c ByCreated) Less(i, j int) bool {
- t1, err := time.Parse(time.RFC3339Nano, c[i].containerInfo.Created)
+ t1, err := time.Parse(time.RFC3339Nano, c[i].ContainerInfo().Created)
if err != nil {
t1 = time.Now()
}
- t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created)
+ t2, _ := time.Parse(time.RFC3339Nano, c[j].ContainerInfo().Created)
if err != nil {
t1 = time.Now()
}
@@ -33,18 +34,18 @@ func (c ByCreated) Less(i, j int) bool {
// the front of the list while containers with links will be sorted after all
// of their dependencies. This sort order ensures that linked containers can
// be started in the correct order.
-func SortByDependencies(containers []Container) ([]Container, error) {
+func SortByDependencies(containers []container.Container) ([]container.Container, error) {
sorter := dependencySorter{}
return sorter.Sort(containers)
}
type dependencySorter struct {
- unvisited []Container
+ unvisited []container.Container
marked map[string]bool
- sorted []Container
+ sorted []container.Container
}
-func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
+func (ds *dependencySorter) Sort(containers []container.Container) ([]container.Container, error) {
ds.unvisited = containers
ds.marked = map[string]bool{}
@@ -57,10 +58,10 @@ func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) {
return ds.sorted, nil
}
-func (ds *dependencySorter) visit(c Container) error {
+func (ds *dependencySorter) visit(c container.Container) error {
if _, ok := ds.marked[c.Name()]; ok {
- return fmt.Errorf("Circular reference to %s", c.Name())
+ return fmt.Errorf("circular reference to %s", c.Name())
}
// Mark any visited node so that circular references can be detected
@@ -83,7 +84,7 @@ func (ds *dependencySorter) visit(c Container) error {
return nil
}
-func (ds *dependencySorter) findUnvisited(name string) *Container {
+func (ds *dependencySorter) findUnvisited(name string) *container.Container {
for _, c := range ds.unvisited {
if c.Name() == name {
return &c
@@ -93,7 +94,7 @@ func (ds *dependencySorter) findUnvisited(name string) *Container {
return nil
}
-func (ds *dependencySorter) removeUnvisited(c Container) {
+func (ds *dependencySorter) removeUnvisited(c container.Container) {
var idx int
for i := range ds.unvisited {
if ds.unvisited[i].Name() == c.Name() {
diff --git a/internal/actions/update_params.go b/pkg/types/update_params.go
similarity index 74%
rename from internal/actions/update_params.go
rename to pkg/types/update_params.go
index ff586c6..8c6fea7 100644
--- a/internal/actions/update_params.go
+++ b/pkg/types/update_params.go
@@ -1,13 +1,12 @@
-package actions
+package types
import (
- t "github.com/containrrr/watchtower/pkg/types"
"time"
)
// UpdateParams contains all different options available to alter the behavior of the Update func
type UpdateParams struct {
- Filter t.Filter
+ Filter Filter
Cleanup bool
NoRestart bool
Timeout time.Duration