mirror of
https://github.com/containrrr/watchtower.git
synced 2025-01-29 18:53:51 +02:00
Merge branch 'master' into all-contributors/add-mbrandau
This commit is contained in:
commit
1d55d229a1
@ -486,6 +486,43 @@
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "aneisch",
|
||||
"name": "Andrew",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6991461?v=4",
|
||||
"profile": "https://github.com/aneisch",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sixcorners",
|
||||
"name": "sixcorners",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/585501?v=4",
|
||||
"profile": "https://github.com/sixcorners",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "piksel",
|
||||
"name": "nils måsén",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/807383?v=4",
|
||||
"profile": "https://piksel.se",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "arnested",
|
||||
"name": "Arne Jørgensen",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/190005?v=4",
|
||||
"profile": "https://arnested.dk",
|
||||
"contributions": [
|
||||
"test",
|
||||
"review"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -137,6 +137,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://github.com/auanasgheps"><img src="https://avatars2.githubusercontent.com/u/20586878?v=4" width="100px;" alt=""/><br /><sub><b>Oliver Cervera</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=auanasgheps" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/victorcmoura"><img src="https://avatars1.githubusercontent.com/u/26290053?v=4" width="100px;" alt=""/><br /><sub><b>Victor Moura</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/mbrandau"><img src="https://avatars3.githubusercontent.com/u/12972798?v=4" width="100px;" alt=""/><br /><sub><b>Maximilian Brandau</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/aneisch"><img src="https://avatars1.githubusercontent.com/u/6991461?v=4" width="100px;" alt=""/><br /><sub><b>Andrew</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=aneisch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sixcorners"><img src="https://avatars0.githubusercontent.com/u/585501?v=4" width="100px;" alt=""/><br /><sub><b>sixcorners</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixcorners" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://piksel.se"><img src="https://avatars2.githubusercontent.com/u/807383?v=4" width="100px;" alt=""/><br /><sub><b>nils måsén</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://arnested.dk"><img src="https://avatars2.githubusercontent.com/u/190005?v=4" width="100px;" alt=""/><br /><sub><b>Arne Jørgensen</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=arnested" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Aarnested" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -113,7 +113,9 @@ func Run(c *cobra.Command, names []string) {
|
||||
runOnce, _ := c.PersistentFlags().GetBool("run-once")
|
||||
|
||||
if runOnce {
|
||||
log.Info("Running a one time update.")
|
||||
if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
|
||||
log.Info("Running a one time update.")
|
||||
}
|
||||
runUpdatesWithNotifications(filter)
|
||||
os.Exit(0)
|
||||
return
|
||||
|
@ -39,7 +39,7 @@ Environment Variable: N/A
|
||||
|
||||
## Time Zone
|
||||
Sets the time zone to be used by WatchTower's logs and the optional Cron scheduling argument (--schedule). If this environment variable is not set, Watchtower will use the default time zone: UTC.
|
||||
To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_.
|
||||
To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_. The timezome can alternatively be set by volume mounting your hosts /etc/timezone file. `-v /etc/timezone:/etc/timezone:ro`
|
||||
|
||||
```
|
||||
Argument: N/A
|
||||
@ -119,7 +119,7 @@ Environment Variable: WATCHTOWER_REVIVE_STOPPED
|
||||
```
|
||||
|
||||
## Poll interval
|
||||
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images.
|
||||
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Either `--schedule` or a poll interval can be defined, but not both.
|
||||
|
||||
```
|
||||
Argument: --interval, -i
|
||||
@ -139,7 +139,11 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
|
||||
```
|
||||
|
||||
## Without updating containers
|
||||
Will only monitor for new images, not update the containers.
|
||||
Will only monitor for new images, not update the containers.
|
||||
|
||||
> ### ⚠️ Please note
|
||||
>
|
||||
> Due to Docker API limitations the latest image will still be pulled from the registry.
|
||||
|
||||
```
|
||||
Argument: --monitor-only
|
||||
@ -192,7 +196,8 @@ Environment Variable: WATCHTOWER_RUN_ONCE
|
||||
```
|
||||
|
||||
## Scheduling
|
||||
[Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both. An example: `--schedule "0 0 4 * * *"`
|
||||
[Cron expression](https://pkg.go.dev/github.com/robfig/cron@v1.2.0?tab=doc#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
|
||||
can be defined, but not both. An example: `--schedule "0 0 4 * * *"`
|
||||
|
||||
```
|
||||
Argument: --schedule, -s
|
||||
|
@ -12,7 +12,7 @@ Or, it can be specified as part of the `docker run` command line:
|
||||
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
|
||||
```
|
||||
|
||||
If you need to include only some containers, pass the `--label-enable` flag on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
|
||||
If you need to [include only containers with the enable label](https://containrrr.github.io/watchtower/arguments/#filter_by_enable_label), pass the `--label-enable` flag or the `WATCTOWER_LABEL_ENABLE` environment variable on startup and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` for the containers you want to watch.
|
||||
|
||||
```docker
|
||||
LABEL com.centurylinklabs.watchtower.enable="true"
|
||||
@ -22,4 +22,4 @@ Or, it can be specified as part of the `docker run` command line:
|
||||
|
||||
```bash
|
||||
docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
|
||||
```
|
||||
```
|
||||
|
@ -46,6 +46,19 @@ docker run -d \
|
||||
--label=com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh" \
|
||||
```
|
||||
|
||||
### Timeouts
|
||||
The timeout for all lifecycle commands is 60 seconds. After that, a timeout will
|
||||
occur, forcing Watchtower to continue the update loop.
|
||||
|
||||
#### Pre-update timeouts
|
||||
|
||||
For the `pre-update` lifecycle command, it is possible to override this timeout to
|
||||
allow the script to finish before forcefully killing it. This is done by adding the
|
||||
label `com.centurylinklabs.watchtower.lifecycle.pre-update-timeout` followed by
|
||||
the timeout expressed in minutes.
|
||||
|
||||
If the label value is explicitly set to `0`, the timeout will be disabled.
|
||||
|
||||
### Execution failure
|
||||
|
||||
The failure of a command to execute, identified by an exit code different than
|
||||
|
@ -8,6 +8,7 @@ The types of notifications to send are set by passing a comma-separated list of
|
||||
- `slack` to send notifications through a Slack webhook
|
||||
- `msteams` to send notifications via MSTeams webhook
|
||||
- `gotify` to send notifications via Gotify
|
||||
- `shoutrrr` to send notifications via [containrrr/shoutrrr](https://github.com/containrrr/shoutrrr)
|
||||
|
||||
> There is currently a [bug](https://github.com/spf13/viper/issues/380) in Viper, which prevents comma-separated slices to be used when using the environment variable. A workaround is available where we instead put quotes around the environment variable value and replace the commas with spaces, as `WATCHTOWER_NOTIFICATIONS="slack msteams"`
|
||||
|
||||
@ -116,3 +117,23 @@ docker run -d \
|
||||
-e WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN="SuperSecretToken" \
|
||||
containrrr/watchtower
|
||||
```
|
||||
|
||||
### [containrrr/shoutrrr](https://github.com/containrrr/shoutrrr)
|
||||
|
||||
To send notifications via shoutrrr, the following command-line options, or their corresponding environment variables, can be set:
|
||||
|
||||
- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used.
|
||||
|
||||
Go to [containrrr.github.io/shoutrrr/services/overview](https://containrrr.github.io/shoutrrr/services/overview) to learn more about the different service URLs you can use.
|
||||
You can define multiple services by space separating the URLs. (See example below)
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
|
||||
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
|
||||
containrrr/watchtower
|
||||
```
|
@ -1,12 +1,17 @@
|
||||
Watchtower supports private Docker image registries. In many cases, accessing a private registry requires a valid username and password (i.e., _credentials_). In order to operate in such an environment, watchtower needs to know the credentials to access the registry.
|
||||
Watchtower supports private Docker image registries. In many cases, accessing a private registry
|
||||
requires a valid username and password (i.e., _credentials_). In order to operate in such an
|
||||
environment, watchtower needs to know the credentials to access the registry.
|
||||
|
||||
The credentials can be provided to watchtower in a configuration file called `config.json`. There are two ways to generate this configuration file:
|
||||
The credentials can be provided to watchtower in a configuration file called `config.json`.
|
||||
There are two ways to generate this configuration file:
|
||||
|
||||
* The configuration file can be created manually.
|
||||
* Call `docker login <REGISTRY_NAME>` and share the resulting configuration file.
|
||||
|
||||
### Create the configuration file manually
|
||||
Create a new configuration file with the following syntax and a base64 encoded username and password `auth` string:
|
||||
Create a new configuration file with the following syntax and a base64 encoded username and
|
||||
password `auth` string:
|
||||
|
||||
```json
|
||||
{
|
||||
"auths": {
|
||||
@ -17,27 +22,40 @@ Create a new configuration file with the following syntax and a base64 encoded u
|
||||
}
|
||||
```
|
||||
|
||||
`<REGISTRY_NAME>` needs to be replaced by the name of your private registry (e.g., `my-private-registry.example.org`)
|
||||
`<REGISTRY_NAME>` needs to be replaced by the name of your private registry
|
||||
(e.g., `my-private-registry.example.org`)
|
||||
|
||||
The required `auth` string can be generated as follows:
|
||||
```bash
|
||||
echo -n 'username:password' | base64
|
||||
```
|
||||
|
||||
When the watchtower Docker container is started, the created configuration file (`<PATH>/config.json` in this example) needs to be passed to the container:
|
||||
> ### ℹ️ Username and Password for GCloud
|
||||
>
|
||||
> For gcloud, we'll use `__json_key` as our username and the content
|
||||
> of `gcloudauth.json` as the password.
|
||||
|
||||
When the watchtower Docker container is started, the created configuration file
|
||||
(`<PATH>/config.json` in this example) needs to be passed to the container:
|
||||
|
||||
```bash
|
||||
docker run [...] -v <PATH>/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 `<PATH_TO_HOME_DIR>/.docker/config.json`. This configuration file can be directly used by watchtower. In this case, the creation of an additional configuration file is not necessary.
|
||||
To pull an image from a private registry, `docker login` needs to be called first, to get access
|
||||
to the registry. The provided credentials are stored in a configuration file called `<PATH_TO_HOME_DIR>/.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 <PATH_TO_HOME_DIR>/.docker/config.json:/config.json containrrr/watchtower
|
||||
```
|
||||
|
||||
When creating the watchtower container via docker-compose, use the following lines:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
[...]
|
||||
|
1
go.mod
1
go.mod
@ -15,6 +15,7 @@ require (
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||
github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13
|
||||
github.com/docker/cli v0.0.0-20190327152802-57b27434ea29
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4
|
||||
|
21
go.sum
21
go.sum
@ -43,6 +43,8 @@ github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6 h1:A7RURps5t4yDU0
|
||||
github.com/cloudflare/cfssl v0.0.0-20190911221928-1a911ca1b1d6/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13 h1:5KIwcRac24xehTL/xrhXNIiI9JnV2Mbfl52OgGbloIM=
|
||||
github.com/containrrr/shoutrrr v0.0.0-20200404203330-157bd996ea13/go.mod h1:eotQeC9bHbsf9eMUnXOU/y5bskegseWNB4PwmxRO7Wc=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@ -61,6 +63,7 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4 h1:34LfsqlE2kEvmGP9qbRoPvOWkmluYGzmlvWVTzwvT0A=
|
||||
github.com/docker/docker v0.0.0-20190404075923-dbe4a30928d4/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
|
||||
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k=
|
||||
@ -78,6 +81,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@ -108,6 +113,8 @@ github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLm
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@ -130,6 +137,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
|
||||
github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
|
||||
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@ -161,6 +170,10 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
@ -284,6 +297,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -291,6 +305,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -298,6 +313,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
@ -313,6 +329,9 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
@ -345,6 +364,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gosrc.io/xmpp v0.1.1 h1:iMtE9W3fx254+4E6rI34AOPJDqWvpfQR6EYaVMzhJ4s=
|
||||
gosrc.io/xmpp v0.1.1/go.mod h1:4JgaXzw4MnEv2sGltONtK3GMhj+h9gpQ7cO8nwbFJLU=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/containrrr/watchtower/pkg/container/mocks"
|
||||
|
||||
cli "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
. "github.com/containrrr/watchtower/internal/actions/mocks"
|
||||
. "github.com/onsi/ginkgo"
|
||||
@ -132,3 +133,14 @@ var _ = Describe("the actions package", func() {
|
||||
})
|
||||
})
|
||||
|
||||
func createMockContainer(id string, name string, image string, created time.Time) container.Container {
|
||||
content := types.ContainerJSON{
|
||||
ContainerJSONBase: &types.ContainerJSONBase{
|
||||
ID: id,
|
||||
Image: image,
|
||||
Name: name,
|
||||
Created: created.String(),
|
||||
},
|
||||
}
|
||||
return *container.NewContainer(&content, nil)
|
||||
}
|
@ -73,7 +73,7 @@ func (client MockClient) GetContainer(containerID string) (container.Container,
|
||||
}
|
||||
|
||||
// ExecuteCommand is a mock method
|
||||
func (client MockClient) ExecuteCommand(containerID string, command string) error {
|
||||
func (client MockClient) ExecuteCommand(containerID string, command string, timeout int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -74,10 +74,13 @@ func stopStaleContainer(container container.Container, client container.Client,
|
||||
return
|
||||
}
|
||||
if params.LifecycleHooks {
|
||||
lifecycle.ExecutePreUpdateCommand(client, container)
|
||||
|
||||
if err := lifecycle.ExecutePreUpdateCommand(client, container); err != nil {
|
||||
log.Error(err)
|
||||
log.Info("Skipping container as the pre-update command failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if err := client.StopContainer(container, params.Timeout); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
@ -30,12 +30,14 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
||||
viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
|
||||
"poll interval (in seconds)")
|
||||
|
||||
flags.StringP("schedule",
|
||||
flags.StringP(
|
||||
"schedule",
|
||||
"s",
|
||||
viper.GetString("WATCHTOWER_SCHEDULE"),
|
||||
"the cron expression which defines when to update")
|
||||
|
||||
flags.DurationP("stop-timeout",
|
||||
flags.DurationP(
|
||||
"stop-timeout",
|
||||
"t",
|
||||
viper.GetDuration("WATCHTOWER_TIMEOUT"),
|
||||
"timeout before a container is forcefully stopped")
|
||||
@ -121,7 +123,7 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
|
||||
"notifications",
|
||||
"n",
|
||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
|
||||
" notification types to send (valid: email, slack, msteams, gotify)")
|
||||
" notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
|
||||
|
||||
flags.StringP(
|
||||
"notifications-level",
|
||||
@ -238,6 +240,12 @@ Should only be used for testing.
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
|
||||
"The Gotify Application required to query the Gotify API")
|
||||
|
||||
flags.StringArrayP(
|
||||
"notification-url",
|
||||
"",
|
||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
|
||||
"The shoutrrr URL to send notifications to")
|
||||
}
|
||||
|
||||
// SetDefaults provides default values for environment variables
|
||||
|
@ -29,9 +29,8 @@ type Client interface {
|
||||
StartContainer(Container) (string, error)
|
||||
RenameContainer(Container, string) error
|
||||
IsContainerStale(Container) (bool, error)
|
||||
ExecuteCommand(containerID string, command string) error
|
||||
ExecuteCommand(containerID string, command string, timeout int) error
|
||||
RemoveImageByID(string) error
|
||||
|
||||
}
|
||||
|
||||
// NewClient returns a new Client instance which can be used to interact with
|
||||
@ -301,7 +300,7 @@ func (client dockerClient) RemoveImageByID(id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (client dockerClient) ExecuteCommand(containerID string, command string) error {
|
||||
func (client dockerClient) ExecuteCommand(containerID string, command string, timeout int) error {
|
||||
bg := context.Background()
|
||||
|
||||
// Create the exec
|
||||
@ -331,7 +330,7 @@ func (client dockerClient) ExecuteCommand(containerID string, command string) er
|
||||
return err
|
||||
}
|
||||
|
||||
var execOutput string
|
||||
var output string
|
||||
if attachErr == nil {
|
||||
defer response.Close()
|
||||
var writer bytes.Buffer
|
||||
@ -339,26 +338,56 @@ func (client dockerClient) ExecuteCommand(containerID string, command string) er
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else if written > 0 {
|
||||
execOutput = strings.TrimSpace(writer.String())
|
||||
output = strings.TrimSpace(writer.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect the exec to get the exit code and print a message if the
|
||||
// exit code is not success.
|
||||
execInspect, err := client.api.ContainerExecInspect(bg, exec.ID)
|
||||
err = client.waitForExecOrTimeout(bg, exec.ID, output, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if execInspect.ExitCode > 0 {
|
||||
log.Errorf("Command exited with code %v.", execInspect.ExitCode)
|
||||
log.Error(execOutput)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client dockerClient) waitForExecOrTimeout(bg context.Context, ID string, execOutput string, timeout int) error {
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(bg, time.Duration(timeout)*time.Minute)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = bg
|
||||
}
|
||||
|
||||
for {
|
||||
execInspect, err := client.api.ContainerExecInspect(ctx, ID)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"exit-code": execInspect.ExitCode,
|
||||
"exec-id": execInspect.ExecID,
|
||||
"running": execInspect.Running,
|
||||
}).Debug("Awaiting timeout or completion")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if execInspect.Running == true {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if len(execOutput) > 0 {
|
||||
log.Infof("Command output:\n%v", execOutput)
|
||||
}
|
||||
if execInspect.ExitCode > 0 {
|
||||
log.Errorf("Command exited with code %v.", execInspect.ExitCode)
|
||||
log.Error(execOutput)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -377,7 +406,6 @@ func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Durat
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containrrr/watchtower/internal/util"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/util"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
)
|
||||
@ -118,6 +119,25 @@ func (c Container) IsWatchtower() bool {
|
||||
return ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
|
||||
}
|
||||
|
||||
// PreUpdateTimeout checks whether a container has a specific timeout set
|
||||
// for how long the pre-update command is allowed to run. This value is expressed
|
||||
// either as an integer, in minutes, or as 0 which will allow the command/script
|
||||
// to run indefinitely. Users should be cautious with the 0 option, as that
|
||||
// could result in watchtower waiting forever.
|
||||
func (c Container) PreUpdateTimeout() int {
|
||||
var minutes int
|
||||
var err error
|
||||
|
||||
val := c.getLabelValueOrEmpty(preUpdateTimeoutLabel)
|
||||
|
||||
minutes, err = strconv.Atoi(val)
|
||||
if err != nil || val == "" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return minutes
|
||||
}
|
||||
|
||||
// StopSignal returns the custom stop signal (if any) that is encoded in the
|
||||
// container's metadata. If the container has not specified a custom stop
|
||||
// signal, the empty string "" is returned.
|
||||
|
@ -1,14 +1,15 @@
|
||||
package container
|
||||
|
||||
const (
|
||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
||||
enableLabel = "com.centurylinklabs.watchtower.enable"
|
||||
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
||||
preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
|
||||
postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
|
||||
preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
|
||||
postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
|
||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
||||
enableLabel = "com.centurylinklabs.watchtower.enable"
|
||||
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
||||
preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
|
||||
postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
|
||||
preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
|
||||
postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
|
||||
preUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout"
|
||||
)
|
||||
|
||||
// GetLifecyclePreCheckCommand returns the pre-check command set in the container metadata or an empty string
|
||||
|
@ -37,7 +37,7 @@ func ExecutePreCheckCommand(client container.Client, container container.Contain
|
||||
}
|
||||
|
||||
log.Info("Executing pre-check command.")
|
||||
if err := client.ExecuteCommand(container.ID(), command); err != nil {
|
||||
if err := client.ExecuteCommand(container.ID(), command, 1); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
@ -51,24 +51,22 @@ func ExecutePostCheckCommand(client container.Client, container container.Contai
|
||||
}
|
||||
|
||||
log.Info("Executing post-check command.")
|
||||
if err := client.ExecuteCommand(container.ID(), command); err != nil {
|
||||
if err := client.ExecuteCommand(container.ID(), command, 1); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
|
||||
func ExecutePreUpdateCommand(client container.Client, container container.Container) {
|
||||
|
||||
func ExecutePreUpdateCommand(client container.Client, container container.Container) error {
|
||||
timeout := container.PreUpdateTimeout()
|
||||
command := container.GetLifecyclePreUpdateCommand()
|
||||
if len(command) == 0 {
|
||||
log.Debug("No pre-update command supplied. Skipping")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Executing pre-update command.")
|
||||
if err := client.ExecuteCommand(container.ID(), command); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return client.ExecuteCommand(container.ID(), command, timeout)
|
||||
}
|
||||
|
||||
// ExecutePostUpdateCommand tries to run the post-update lifecycle hook for a single container.
|
||||
@ -86,7 +84,7 @@ func ExecutePostUpdateCommand(client container.Client, newContainerID string) {
|
||||
}
|
||||
|
||||
log.Info("Executing post-update command.")
|
||||
if err := client.ExecuteCommand(newContainerID, command); err != nil {
|
||||
if err := client.ExecuteCommand(newContainerID, command, 1); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ func NewNotifier(c *cobra.Command) *Notifier {
|
||||
acceptedLogLevels := slackrus.LevelThreshold(logLevel)
|
||||
|
||||
// Parse types and create notifiers.
|
||||
types, _ := f.GetStringSlice("notifications")
|
||||
|
||||
types, err := f.GetStringSlice("notifications")
|
||||
if err != nil {
|
||||
log.WithField("could not read notifications argument", log.Fields{ "Error": err }).Fatal()
|
||||
}
|
||||
for _, t := range types {
|
||||
var tn ty.Notifier
|
||||
switch t {
|
||||
@ -40,6 +42,8 @@ func NewNotifier(c *cobra.Command) *Notifier {
|
||||
tn = newMsTeamsNotifier(c, acceptedLogLevels)
|
||||
case gotifyType:
|
||||
tn = newGotifyNotifier(c, acceptedLogLevels)
|
||||
case shoutrrrType:
|
||||
tn = newShoutrrrNotifier(c, acceptedLogLevels)
|
||||
default:
|
||||
log.Fatalf("Unknown notification type %q", t)
|
||||
}
|
||||
|
93
pkg/notifications/shoutrrr.go
Normal file
93
pkg/notifications/shoutrrr.go
Normal file
@ -0,0 +1,93 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containrrr/shoutrrr"
|
||||
"github.com/containrrr/shoutrrr/pkg/router"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
shoutrrrType = "shoutrrr"
|
||||
)
|
||||
|
||||
// Implements Notifier, logrus.Hook
|
||||
type shoutrrrTypeNotifier struct {
|
||||
Urls []string
|
||||
Router *router.ServiceRouter
|
||||
entries []*log.Entry
|
||||
logLevels []log.Level
|
||||
}
|
||||
|
||||
func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
flags := c.PersistentFlags()
|
||||
|
||||
urls, _ := flags.GetStringArray("notification-url")
|
||||
r, _ := shoutrrr.CreateSender(urls...)
|
||||
|
||||
n := &shoutrrrTypeNotifier{
|
||||
Urls: urls,
|
||||
Router: r,
|
||||
logLevels: acceptedLogLevels,
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) buildMessage(entries []*log.Entry) string {
|
||||
body := ""
|
||||
for _, entry := range entries {
|
||||
body += entry.Time.Format("2006-01-02 15:04:05") + " (" + entry.Level.String() + "): " + entry.Message + "\r\n"
|
||||
// We don't use fields in watchtower, so don't bother sending them.
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) {
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
msg := e.buildMessage(entries)
|
||||
go func() {
|
||||
errs := e.Router.Send(msg, nil)
|
||||
|
||||
for i, err := range errs {
|
||||
if err != nil {
|
||||
// Use fmt so it doesn't trigger another notification.
|
||||
fmt.Println("Failed to send notification via shoutrrr (url="+e.Urls[i]+"): ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) StartNotification() {
|
||||
if e.entries == nil {
|
||||
e.entries = make([]*log.Entry, 0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) SendNotification() {
|
||||
if e.entries == nil || len(e.entries) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e.sendEntries(e.entries)
|
||||
e.entries = nil
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) Levels() []log.Level {
|
||||
return e.logLevels
|
||||
}
|
||||
|
||||
func (e *shoutrrrTypeNotifier) Fire(entry *log.Entry) error {
|
||||
if e.entries != nil {
|
||||
e.entries = append(e.entries, entry)
|
||||
} else {
|
||||
// Log output generated outside a cycle is sent immediately.
|
||||
e.sendEntries([]*log.Entry{entry})
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user