mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
preparations for soft deprecation of legacy notification args (#1377)
Co-authored-by: Simon Aronsson <simme@arcticbit.se>
This commit is contained in:
parent
2102a056de
commit
cb555f539d
18 changed files with 505 additions and 167 deletions
111
cmd/notify-upgrade.go
Normal file
111
cmd/notify-upgrade.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Package cmd contains the watchtower (sub-)commands
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/flags"
|
||||
"github.com/containrrr/watchtower/pkg/container"
|
||||
"github.com/containrrr/watchtower/pkg/notifications"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var notifyUpgradeCommand = NewNotifyUpgradeCommand()
|
||||
|
||||
// NewNotifyUpgradeCommand creates the notify upgrade command for watchtower
|
||||
func NewNotifyUpgradeCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "notify-upgrade",
|
||||
Short: "Upgrade legacy notification configuration to shoutrrr URLs",
|
||||
Run: runNotifyUpgrade,
|
||||
}
|
||||
}
|
||||
|
||||
func runNotifyUpgrade(cmd *cobra.Command, args []string) {
|
||||
if err := runNotifyUpgradeE(cmd, args); err != nil {
|
||||
logf("Notification upgrade failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runNotifyUpgradeE(cmd *cobra.Command, _ []string) error {
|
||||
f := cmd.Flags()
|
||||
flags.ProcessFlagAliases(f)
|
||||
|
||||
notifier = notifications.NewNotifier(cmd)
|
||||
urls := notifier.GetURLs()
|
||||
|
||||
logf("Found notification configurations for: %v", strings.Join(notifier.GetNames(), ", "))
|
||||
|
||||
outFile, err := os.CreateTemp("/", "watchtower-notif-urls-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %v", err)
|
||||
}
|
||||
logf("Writing notification URLs to %v", outFile.Name())
|
||||
logf("")
|
||||
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("WATCHTOWER_NOTIFICATION_URL=")
|
||||
|
||||
for i, u := range urls {
|
||||
if i != 0 {
|
||||
sb.WriteRune(' ')
|
||||
}
|
||||
sb.WriteString(u)
|
||||
}
|
||||
|
||||
_, err = fmt.Fprint(outFile, sb.String())
|
||||
tryOrLog(err, "Failed to write to output file")
|
||||
|
||||
tryOrLog(outFile.Sync(), "Failed to sync output file")
|
||||
tryOrLog(outFile.Close(), "Failed to close output file")
|
||||
|
||||
containerID := "<CONTAINER>"
|
||||
cid, err := container.GetRunningContainerID()
|
||||
tryOrLog(err, "Failed to get running container ID")
|
||||
if cid != "" {
|
||||
containerID = cid.ShortID()
|
||||
}
|
||||
logf("To get the environment file, use:")
|
||||
logf("cp %v:%v ./watchtower-notifications.env", containerID, outFile.Name())
|
||||
logf("")
|
||||
logf("Note: This file will be removed in 5 minutes or when this container is stopped!")
|
||||
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
time.AfterFunc(5*time.Minute, func() {
|
||||
signalChannel <- syscall.SIGALRM
|
||||
})
|
||||
|
||||
signal.Notify(signalChannel, os.Interrupt)
|
||||
signal.Notify(signalChannel, syscall.SIGTERM)
|
||||
|
||||
switch <-signalChannel {
|
||||
case syscall.SIGALRM:
|
||||
logf("Timed out!")
|
||||
case os.Interrupt, syscall.SIGTERM:
|
||||
logf("Stopping...")
|
||||
default:
|
||||
}
|
||||
|
||||
if err := os.Remove(outFile.Name()); err != nil {
|
||||
logf("Failed to remove file, it may still be present in the container image! Error: %v", err)
|
||||
} else {
|
||||
logf("Environment file has been removed.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryOrLog(err error, message string) {
|
||||
if err != nil {
|
||||
logf("%v: %v\n", message, err)
|
||||
}
|
||||
}
|
||||
|
||||
func logf(format string, v ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...))
|
||||
}
|
|
@ -66,6 +66,7 @@ func init() {
|
|||
|
||||
// Execute the root func and exit in case of errors
|
||||
func Execute() {
|
||||
rootCmd.AddCommand(notifyUpgradeCommand)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -139,6 +140,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
|
|||
})
|
||||
|
||||
notifier = notifications.NewNotifier(cmd)
|
||||
notifier.AddLogHook()
|
||||
}
|
||||
|
||||
// Run is the main execution flow of the command
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
# Notifications
|
||||
|
||||
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging
|
||||
system, [logrus](http://github.com/sirupsen/logrus). The types of notifications to send are set by passing a
|
||||
comma-separated list of values to the `--notifications` option
|
||||
(or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
|
||||
|
||||
- `email` to send notifications via e-mail
|
||||
- `slack` to send notifications through a Slack webhook
|
||||
- `msteams` to send notifications via MSTeams webhook
|
||||
- `gotify` to send notifications via Gotify
|
||||
- `shoutrrr` to send notifications via [containrrr/shoutrrr](https://github.com/containrrr/shoutrrr)
|
||||
system, [logrus](http://github.com/sirupsen/logrus).
|
||||
|
||||
!!! note "Using multiple notifications with environment variables"
|
||||
There is currently a bug in Viper (https://github.com/spf13/viper/issues/380), which prevents comma-separated slices to
|
||||
|
@ -31,7 +23,221 @@ comma-separated list of values to the `--notifications` option
|
|||
- `notification-title-tag` (env. `WATCHTOWER_NOTIFICATION_TITLE_TAG`): Prefix to include in the title. Useful when running multiple watchtowers.
|
||||
- `notification-skip-title` (env. `WATCHTOWER_NOTIFICATION_SKIP_TITLE`): Do not pass the title param to notifications. This will not pass a dynamic title override to notification services. If no title is configured for the service, it will remove the title all together.
|
||||
|
||||
## Available services
|
||||
## [shoutrrr](https://github.com/containrrr/shoutrrr) notifications
|
||||
|
||||
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. This option can also reference a file, in which case the contents of the file are used.
|
||||
|
||||
|
||||
Go to [containrrr.dev/shoutrrr/v0.6/services/overview](https://containrrr.dev/shoutrrr/v0.6/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)
|
||||
|
||||
You can customize the message posted by setting a template.
|
||||
|
||||
- `--notification-template` (env. `WATCHTOWER_NOTIFICATION_TEMPLATE`): The template used for the message.
|
||||
|
||||
The template is a Go [template](https://golang.org/pkg/text/template/) that either format a list
|
||||
of [log entries](https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry) or a `notification.Data` struct.
|
||||
|
||||
Simple templates are used unless the `notification-report` flag is specified:
|
||||
|
||||
- `--notification-report` (env. `WATCHTOWER_NOTIFICATION_REPORT`): Use the session report as the notification template data.
|
||||
|
||||
## Simple templates
|
||||
|
||||
The default value if not set is `{{range .}}{{.Message}}{{println}}{{end}}`. The example below uses a template that also
|
||||
outputs timestamp and log level.
|
||||
|
||||
!!! tip "Custom date format"
|
||||
If you want to adjust the date/time format it must show how the
|
||||
[reference time](https://golang.org/pkg/time/#pkg-constants) (_Mon Jan 2 15:04:05 MST 2006_) would be displayed in your
|
||||
custom format.
|
||||
i.e., The day of the year has to be 1, the month has to be 2 (february), the hour 3 (or 15 for 24h time) etc.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
|
||||
-e WATCHTOWER_NOTIFICATION_TEMPLATE="{{range .}}{{.Time.Format \"2006-01-02 15:04:05\"}} ({{.Level}}): {{.Message}}{{println}}{{end}}" \
|
||||
containrrr/watchtower
|
||||
```
|
||||
|
||||
## Report templates
|
||||
|
||||
The default template for report notifications are the following:
|
||||
```go
|
||||
{{- if .Report -}}
|
||||
{{- with .Report -}}
|
||||
{{- if ( or .Updated .Failed ) -}}
|
||||
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
|
||||
{{- range .Updated}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
|
||||
{{- end -}}
|
||||
{{- range .Fresh}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}
|
||||
{{- end -}}
|
||||
{{- range .Skipped}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- range .Failed}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
It will be used to send a summary of every session if there are any containers that were updated or which failed to update.
|
||||
|
||||
!!! note "Skipping notifications"
|
||||
Whenever the result of applying the template results in an empty string, no notifications will
|
||||
be sent. This is by default used to limit the notifications to only be sent when there something noteworthy occurred.
|
||||
|
||||
You can replace `{{- if ( or .Updated .Failed ) -}}` with any logic you want to decide when to send the notifications.
|
||||
|
||||
Example using a custom report template that always sends a session report after each run:
|
||||
|
||||
=== "docker run"
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e WATCHTOWER_NOTIFICATION_REPORT="true"
|
||||
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
|
||||
-e WATCHTOWER_NOTIFICATION_TEMPLATE="
|
||||
{{- if .Report -}}
|
||||
{{- with .Report -}}
|
||||
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
|
||||
{{- range .Updated}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
|
||||
{{- end -}}
|
||||
{{- range .Fresh}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}
|
||||
{{- end -}}
|
||||
{{- range .Skipped}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- range .Failed}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
|
||||
{{- end -}}
|
||||
" \
|
||||
containrrr/watchtower
|
||||
```
|
||||
|
||||
=== "docker-compose"
|
||||
|
||||
``` yaml
|
||||
version: "3"
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env:
|
||||
WATCHTOWER_NOTIFICATION_REPORT: "true"
|
||||
WATCHTOWER_NOTIFICATION_URL: >
|
||||
discord://token@channel
|
||||
slack://watchtower@token-a/token-b/token-c
|
||||
WATCHTOWER_NOTIFICATION_TEMPLATE: |
|
||||
{{- if .Report -}}
|
||||
{{- with .Report -}}
|
||||
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
|
||||
{{- range .Updated}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
|
||||
{{- end -}}
|
||||
{{- range .Fresh}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}
|
||||
{{- end -}}
|
||||
{{- range .Skipped}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- range .Failed}}
|
||||
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
## Legacy notifications
|
||||
|
||||
For backwards compatibility, the notifications can also be configured using legacy notification options. These will automatically be converted to shoutrrr URLs when used.
|
||||
The types of notifications to send are set by passing a comma-separated list of values to the `--notifications` option
|
||||
(or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
|
||||
|
||||
- `email` to send notifications via e-mail
|
||||
- `slack` to send notifications through a Slack webhook
|
||||
- `msteams` to send notifications via MSTeams webhook
|
||||
- `gotify` to send notifications via Gotify
|
||||
|
||||
### `notify-upgrade`
|
||||
If watchtower is started with `notify-upgrade` as it's first argument, it will generate a .env file with your current legacy notification options converted to shoutrrr URLs.
|
||||
|
||||
=== "docker run"
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e WATCHTOWER_NOTIFICATIONS=slack \
|
||||
-e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
|
||||
containrrr/watchtower \
|
||||
notify-upgrade
|
||||
```
|
||||
|
||||
=== "docker-compose.yml"
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env:
|
||||
WATCHTOWER_NOTIFICATIONS: slack
|
||||
WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy
|
||||
command: notify-upgrade
|
||||
```
|
||||
|
||||
|
||||
You can then copy this file from the container (a message with the full command to do so will be logged) and use it with your current setup:
|
||||
|
||||
=== "docker run"
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--env-file watchtower-notifications.env \
|
||||
containrrr/watchtower
|
||||
```
|
||||
|
||||
=== "docker-compose.yml"
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file:
|
||||
- watchtower-notifications.env
|
||||
```
|
||||
|
||||
### Email
|
||||
|
||||
|
@ -177,41 +383,3 @@ docker run -d \
|
|||
|
||||
If you want to disable TLS verification for the Gotify instance, you can use either `-e WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY=true` or `--notification-gotify-tls-skip-verify`.
|
||||
|
||||
### [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. This option can also reference a file, in which case the contents of the file are used.
|
||||
|
||||
|
||||
Go to [containrrr.dev/shoutrrr/v0.6/services/overview](https://containrrr.dev/shoutrrr/v0.6/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)
|
||||
|
||||
You can customize the message posted by setting a template.
|
||||
|
||||
- `--notification-template` (env. `WATCHTOWER_NOTIFICATION_TEMPLATE`): The template used for the message.
|
||||
|
||||
The template is a Go [template](https://golang.org/pkg/text/template/) and that format a list
|
||||
of [log entries](https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry).
|
||||
|
||||
The default value if not set is `{{range .}}{{.Message}}{{println}}{{end}}`. The example below uses a template that also
|
||||
outputs timestamp and log level.
|
||||
|
||||
!!! tip "Custom date format"
|
||||
If you want to adjust the date/time format it must show how the
|
||||
[reference time](https://golang.org/pkg/time/#pkg-constants) (_Mon Jan 2 15:04:05 MST 2006_) would be displayed in your
|
||||
custom format.
|
||||
i.e., The day of the year has to be 1, the month has to be 2 (february), the hour 3 (or 15 for 24h time) etc.
|
||||
|
||||
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" \
|
||||
-e WATCHTOWER_NOTIFICATION_TEMPLATE="{{range .}}{{.Time.Format \"2006-01-02 15:04:05\"}} ({{.Level}}): {{.Message}}{{println}}{{end}}" \
|
||||
containrrr/watchtower
|
||||
```
|
||||
|
|
2
go.mod
2
go.mod
|
@ -8,7 +8,6 @@ require (
|
|||
github.com/docker/distribution v2.8.1+incompatible
|
||||
github.com/docker/docker v20.10.20+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.22.1
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
|
@ -36,7 +35,6 @@ require (
|
|||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -204,10 +204,6 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P
|
|||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
|
|
29
pkg/container/cgroup_id.go
Normal file
29
pkg/container/cgroup_id.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
)
|
||||
|
||||
var dockerContainerPattern = regexp.MustCompile(`[0-9]+:.*:/docker/([a-f|0-9]{64})`)
|
||||
|
||||
// GetRunningContainerID tries to resolve the current container ID from the current process cgroup information
|
||||
func GetRunningContainerID() (cid types.ContainerID, err error) {
|
||||
file, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", os.Getpid()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return getRunningContainerIDFromString(string(file)), nil
|
||||
}
|
||||
|
||||
func getRunningContainerIDFromString(s string) types.ContainerID {
|
||||
matches := dockerContainerPattern.FindStringSubmatch(s)
|
||||
if len(matches) < 2 {
|
||||
return ""
|
||||
}
|
||||
return types.ContainerID(matches[1])
|
||||
}
|
40
pkg/container/cgroup_id_test.go
Normal file
40
pkg/container/cgroup_id_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GetRunningContainerID", func() {
|
||||
When("a matching container ID is found", func() {
|
||||
It("should return that container ID", func() {
|
||||
cid := getRunningContainerIDFromString(`
|
||||
15:name=systemd:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
14:misc:/
|
||||
13:rdma:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
12:pids:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
11:hugetlb:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
10:net_prio:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
9:perf_event:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
8:net_cls:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
7:freezer:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
6:devices:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
5:blkio:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
4:cpuacct:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
3:cpu:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
2:cpuset:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
1:memory:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
0::/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
`)
|
||||
Expect(cid).To(BeEquivalentTo(`991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377`))
|
||||
})
|
||||
})
|
||||
When("no matching container ID could be found", func() {
|
||||
It("should return that container ID", func() {
|
||||
cid := getRunningContainerIDFromString(`14:misc:/`)
|
||||
Expect(cid).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
//
|
|
@ -1,3 +1,4 @@
|
|||
// Package container contains code related to dealing with docker containers
|
||||
package container
|
||||
|
||||
import (
|
||||
|
|
|
@ -15,17 +15,16 @@ const (
|
|||
)
|
||||
|
||||
type emailTypeNotifier struct {
|
||||
From, To string
|
||||
Server, User, Password, SubjectTag string
|
||||
Port int
|
||||
tlsSkipVerify bool
|
||||
entries []*log.Entry
|
||||
logLevels []log.Level
|
||||
delay time.Duration
|
||||
From, To string
|
||||
Server, User, Password string
|
||||
Port int
|
||||
tlsSkipVerify bool
|
||||
entries []*log.Entry
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newEmailNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
from, _ := flags.GetString("notification-email-from")
|
||||
to, _ := flags.GetString("notification-email-to")
|
||||
|
@ -35,7 +34,6 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
port, _ := flags.GetInt("notification-email-server-port")
|
||||
tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
|
||||
delay, _ := flags.GetInt("notification-email-delay")
|
||||
subjecttag, _ := flags.GetString("notification-email-subjecttag")
|
||||
|
||||
n := &emailTypeNotifier{
|
||||
entries: []*log.Entry{},
|
||||
|
@ -46,22 +44,19 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
Password: password,
|
||||
Port: port,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
logLevels: acceptedLogLevels,
|
||||
delay: time.Duration(delay) * time.Second,
|
||||
SubjectTag: subjecttag,
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (e *emailTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
conf := &shoutrrrSmtp.Config{
|
||||
FromAddress: e.From,
|
||||
FromName: "Watchtower",
|
||||
ToAddresses: []string{e.To},
|
||||
Port: uint16(e.Port),
|
||||
Host: e.Server,
|
||||
Subject: e.getSubject(c, title),
|
||||
Username: e.User,
|
||||
Password: e.Password,
|
||||
UseStartTLS: !e.tlsSkipVerify,
|
||||
|
@ -84,11 +79,3 @@ func (e *emailTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
func (e *emailTypeNotifier) GetDelay() time.Duration {
|
||||
return e.delay
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) getSubject(_ *cobra.Command, title string) string {
|
||||
if e.SubjectTag != "" {
|
||||
return e.SubjectTag + " " + title
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@ type gotifyTypeNotifier struct {
|
|||
gotifyURL string
|
||||
gotifyAppToken string
|
||||
gotifyInsecureSkipVerify bool
|
||||
logLevels []log.Level
|
||||
}
|
||||
|
||||
func newGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newGotifyNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
apiURL := getGotifyURL(flags)
|
||||
token := getGotifyToken(flags)
|
||||
|
@ -34,7 +33,6 @@ func newGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertibleNotifi
|
|||
gotifyURL: apiURL,
|
||||
gotifyAppToken: token,
|
||||
gotifyInsecureSkipVerify: skipVerify,
|
||||
logLevels: levels,
|
||||
}
|
||||
|
||||
return n
|
||||
|
@ -62,7 +60,7 @@ func getGotifyURL(flags *pflag.FlagSet) string {
|
|||
return gotifyURL
|
||||
}
|
||||
|
||||
func (n *gotifyTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
apiURL, err := url.Parse(n.gotifyURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -72,7 +70,6 @@ func (n *gotifyTypeNotifier) GetURL(c *cobra.Command, title string) (string, err
|
|||
Host: apiURL.Host,
|
||||
Path: apiURL.Path,
|
||||
DisableTLS: apiURL.Scheme == "http",
|
||||
Title: title,
|
||||
Token: n.gotifyAppToken,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,12 @@ const (
|
|||
|
||||
type msTeamsTypeNotifier struct {
|
||||
webHookURL string
|
||||
levels []log.Level
|
||||
data bool
|
||||
}
|
||||
|
||||
func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
func newMsTeamsNotifier(cmd *cobra.Command) t.ConvertibleNotifier {
|
||||
|
||||
flags := cmd.PersistentFlags()
|
||||
flags := cmd.Flags()
|
||||
|
||||
webHookURL, _ := flags.GetString("notification-msteams-hook")
|
||||
if len(webHookURL) <= 0 {
|
||||
|
@ -30,7 +29,6 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
|
|||
|
||||
withData, _ := flags.GetBool("notification-msteams-data")
|
||||
n := &msTeamsTypeNotifier{
|
||||
levels: acceptedLogLevels,
|
||||
webHookURL: webHookURL,
|
||||
data: withData,
|
||||
}
|
||||
|
@ -38,7 +36,7 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
|
|||
return n
|
||||
}
|
||||
|
||||
func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
webhookURL, err := url.Parse(n.webHookURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -50,7 +48,6 @@ func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command, title string) (string, er
|
|||
}
|
||||
|
||||
config.Color = ColorHex
|
||||
config.Title = title
|
||||
|
||||
return config.GetURL().String(), nil
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ import (
|
|||
"time"
|
||||
|
||||
ty "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/johntdyer/slackrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewNotifier creates and returns a new Notifier, using global configuration.
|
||||
func NewNotifier(c *cobra.Command) ty.Notifier {
|
||||
f := c.PersistentFlags()
|
||||
f := c.Flags()
|
||||
|
||||
level, _ := f.GetString("notifications-level")
|
||||
logLevel, err := log.ParseLevel(level)
|
||||
|
@ -21,25 +20,19 @@ func NewNotifier(c *cobra.Command) ty.Notifier {
|
|||
log.Fatalf("Notifications invalid log level: %s", err.Error())
|
||||
}
|
||||
|
||||
levels := slackrus.LevelThreshold(logLevel)
|
||||
// slackrus does not allow log level TRACE, even though it's an accepted log level for logrus
|
||||
if len(levels) == 0 {
|
||||
log.Fatalf("Unsupported notification log level provided: %s", level)
|
||||
}
|
||||
|
||||
reportTemplate, _ := f.GetBool("notification-report")
|
||||
stdout, _ := f.GetBool("notification-log-stdout")
|
||||
tplString, _ := f.GetString("notification-template")
|
||||
urls, _ := f.GetStringArray("notification-url")
|
||||
|
||||
data := GetTemplateData(c)
|
||||
urls, delay := AppendLegacyUrls(urls, c, data.Title)
|
||||
urls, delay := AppendLegacyUrls(urls, c)
|
||||
|
||||
return newShoutrrrNotifier(tplString, levels, !reportTemplate, data, delay, stdout, urls...)
|
||||
return createNotifier(urls, logLevel, tplString, !reportTemplate, data, stdout, delay)
|
||||
}
|
||||
|
||||
// AppendLegacyUrls creates shoutrrr equivalent URLs from legacy notification flags
|
||||
func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string, time.Duration) {
|
||||
func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time.Duration) {
|
||||
|
||||
// Parse types and create notifiers.
|
||||
types, err := cmd.Flags().GetStringSlice("notifications")
|
||||
|
@ -56,13 +49,13 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string
|
|||
|
||||
switch t {
|
||||
case emailType:
|
||||
legacyNotifier = newEmailNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newEmailNotifier(cmd)
|
||||
case slackType:
|
||||
legacyNotifier = newSlackNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newSlackNotifier(cmd)
|
||||
case msTeamsType:
|
||||
legacyNotifier = newMsTeamsNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newMsTeamsNotifier(cmd)
|
||||
case gotifyType:
|
||||
legacyNotifier = newGotifyNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newGotifyNotifier(cmd)
|
||||
case shoutrrrType:
|
||||
continue
|
||||
default:
|
||||
|
@ -71,7 +64,7 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string
|
|||
continue
|
||||
}
|
||||
|
||||
shoutrrrURL, err := legacyNotifier.GetURL(cmd, title)
|
||||
shoutrrrURL, err := legacyNotifier.GetURL(cmd)
|
||||
if err != nil {
|
||||
log.Fatal("failed to create notification config: ", err)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package notifications_test
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/cmd"
|
||||
|
@ -147,11 +146,9 @@ var _ = Describe("notifications", func() {
|
|||
channel := "123456789"
|
||||
token := "abvsihdbau"
|
||||
color := notifications.ColorInt
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
username := "containrrrbot"
|
||||
iconURL := "https://containrrr.dev/watchtower-sq180.png"
|
||||
expected := fmt.Sprintf("discord://%s@%s?color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&title=%s&username=watchtower", token, channel, color, title)
|
||||
expected := fmt.Sprintf("discord://%s@%s?color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&username=watchtower", token, channel, color)
|
||||
buildArgs := func(url string) []string {
|
||||
return []string{
|
||||
"--notifications",
|
||||
|
@ -172,7 +169,7 @@ var _ = Describe("notifications", func() {
|
|||
When("icon URL and username are specified", func() {
|
||||
It("should return the expected URL", func() {
|
||||
hookURL := fmt.Sprintf("https://%s/api/webhooks/%s/%s/slack", "discord.com", channel, token)
|
||||
expectedOutput := fmt.Sprintf("discord://%s@%s?avatar=%s&color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&title=%s&username=%s", token, channel, url.QueryEscape(iconURL), color, title, username)
|
||||
expectedOutput := fmt.Sprintf("discord://%s@%s?avatar=%s&color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&username=%s", token, channel, url.QueryEscape(iconURL), color, username)
|
||||
expectedDelay := time.Duration(7) * time.Second
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -199,8 +196,6 @@ var _ = Describe("notifications", func() {
|
|||
tokenB := "BBBBBBBBB"
|
||||
tokenC := "123456789123456789123456"
|
||||
color := url.QueryEscape(notifications.ColorHex)
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
iconURL := "https://containrrr.dev/watchtower-sq180.png"
|
||||
iconEmoji := "whale"
|
||||
|
||||
|
@ -208,7 +203,7 @@ var _ = Describe("notifications", func() {
|
|||
It("should return the expected URL", func() {
|
||||
|
||||
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s&title=%s", tokenA, tokenB, tokenC, username, color, url.QueryEscape(iconURL), title)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s", tokenA, tokenB, tokenC, username, color, url.QueryEscape(iconURL))
|
||||
expectedDelay := time.Duration(7) * time.Second
|
||||
|
||||
args := []string{
|
||||
|
@ -231,7 +226,7 @@ var _ = Describe("notifications", func() {
|
|||
When("icon emoji is specified", func() {
|
||||
It("should return the expected URL", func() {
|
||||
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s&title=%s", tokenA, tokenB, tokenC, username, color, iconEmoji, title)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s", tokenA, tokenB, tokenC, username, color, iconEmoji)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -258,10 +253,8 @@ var _ = Describe("notifications", func() {
|
|||
|
||||
token := "aaa"
|
||||
host := "shoutrrr.local"
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
|
||||
expectedOutput := fmt.Sprintf("gotify://%s/%s?title=%s", host, token, title)
|
||||
expectedOutput := fmt.Sprintf("gotify://%s/%s?title=", host, token)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -287,11 +280,9 @@ var _ = Describe("notifications", func() {
|
|||
tokenB := "33333333012222222222333333333344"
|
||||
tokenC := "44444444-4444-4444-8444-cccccccccccc"
|
||||
color := url.QueryEscape(notifications.ColorHex)
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
|
||||
hookURL := fmt.Sprintf("https://outlook.office.com/webhook/%s/IncomingWebhook/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("teams://%s/%s/%s?color=%s&title=%s", tokenA, tokenB, tokenC, color, title)
|
||||
expectedOutput := fmt.Sprintf("teams://%s/%s/%s?color=%s", tokenA, tokenB, tokenC, color)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -362,18 +353,12 @@ var _ = Describe("notifications", func() {
|
|||
})
|
||||
|
||||
func buildExpectedURL(username string, password string, host string, port int, from string, to string, auth string) string {
|
||||
hostname, err := os.Hostname()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
subject := fmt.Sprintf("Watchtower updates on %s", hostname)
|
||||
|
||||
var template = "smtp://%s:%s@%s:%d/?auth=%s&fromaddress=%s&fromname=Watchtower&subject=%s&toaddresses=%s"
|
||||
var template = "smtp://%s:%s@%s:%d/?auth=%s&fromaddress=%s&fromname=Watchtower&subject=&toaddresses=%s"
|
||||
return fmt.Sprintf(template,
|
||||
url.QueryEscape(username),
|
||||
url.QueryEscape(password),
|
||||
host, port, auth,
|
||||
url.QueryEscape(from),
|
||||
url.QueryEscape(subject),
|
||||
url.QueryEscape(to))
|
||||
}
|
||||
|
||||
|
@ -385,8 +370,7 @@ func testURL(args []string, expectedURL string, expectedDelay time.Duration) {
|
|||
|
||||
Expect(command.ParseFlags(args)).To(Succeed())
|
||||
|
||||
data := notifications.GetTemplateData(command)
|
||||
urls, delay := notifications.AppendLegacyUrls([]string{}, command, data.Title)
|
||||
urls, delay := notifications.AppendLegacyUrls([]string{}, command)
|
||||
|
||||
Expect(urls).To(ContainElement(expectedURL))
|
||||
Expect(delay).To(Equal(expectedDelay))
|
||||
|
|
|
@ -32,13 +32,15 @@ type shoutrrrTypeNotifier struct {
|
|||
Urls []string
|
||||
Router router
|
||||
entries []*log.Entry
|
||||
logLevels []log.Level
|
||||
logLevel log.Level
|
||||
template *template.Template
|
||||
messages chan string
|
||||
done chan bool
|
||||
legacyTemplate bool
|
||||
params *types.Params
|
||||
data StaticData
|
||||
receiving bool
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
// GetScheme returns the scheme part of a Shoutrrr URL
|
||||
|
@ -59,18 +61,24 @@ func (n *shoutrrrTypeNotifier) GetNames() []string {
|
|||
return names
|
||||
}
|
||||
|
||||
func newShoutrrrNotifier(tplString string, levels []log.Level, legacy bool, data StaticData, delay time.Duration, stdout bool, urls ...string) t.Notifier {
|
||||
|
||||
notifier := createNotifier(urls, levels, tplString, legacy, data, stdout)
|
||||
log.AddHook(notifier)
|
||||
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
go sendNotifications(notifier, delay)
|
||||
|
||||
return notifier
|
||||
// GetNames returns a list of URLs for notification services that has been added
|
||||
func (n *shoutrrrTypeNotifier) GetURLs() []string {
|
||||
return n.Urls
|
||||
}
|
||||
|
||||
func createNotifier(urls []string, levels []log.Level, tplString string, legacy bool, data StaticData, stdout bool) *shoutrrrTypeNotifier {
|
||||
// AddLogHook adds the notifier as a receiver of log messages and starts a go func for processing them
|
||||
func (n *shoutrrrTypeNotifier) AddLogHook() {
|
||||
if n.receiving {
|
||||
return
|
||||
}
|
||||
n.receiving = true
|
||||
log.AddHook(n)
|
||||
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
go sendNotifications(n)
|
||||
}
|
||||
|
||||
func createNotifier(urls []string, level log.Level, tplString string, legacy bool, data StaticData, stdout bool, delay time.Duration) *shoutrrrTypeNotifier {
|
||||
tpl, err := getShoutrrrTemplate(tplString, legacy)
|
||||
if err != nil {
|
||||
log.Errorf("Could not use configured notification template: %s. Using default template", err)
|
||||
|
@ -97,7 +105,7 @@ func createNotifier(urls []string, levels []log.Level, tplString string, legacy
|
|||
Router: r,
|
||||
messages: make(chan string, 1),
|
||||
done: make(chan bool),
|
||||
logLevels: levels,
|
||||
logLevel: level,
|
||||
template: tpl,
|
||||
legacyTemplate: legacy,
|
||||
data: data,
|
||||
|
@ -105,9 +113,9 @@ func createNotifier(urls []string, levels []log.Level, tplString string, legacy
|
|||
}
|
||||
}
|
||||
|
||||
func sendNotifications(n *shoutrrrTypeNotifier, delay time.Duration) {
|
||||
func sendNotifications(n *shoutrrrTypeNotifier) {
|
||||
for msg := range n.messages {
|
||||
time.Sleep(delay)
|
||||
time.Sleep(n.delay)
|
||||
errs := n.Router.Send(msg, n.params)
|
||||
|
||||
for i, err := range errs {
|
||||
|
@ -180,7 +188,7 @@ func (n *shoutrrrTypeNotifier) Close() {
|
|||
|
||||
// Levels return what log levels trigger notifications
|
||||
func (n *shoutrrrTypeNotifier) Levels() []log.Level {
|
||||
return n.logLevels
|
||||
return log.AllLevels[:n.logLevel+1]
|
||||
}
|
||||
|
||||
// Fire is the hook that logrus calls on a new log message
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var allButTrace = logrus.AllLevels[0:logrus.TraceLevel]
|
||||
var allButTrace = logrus.DebugLevel
|
||||
|
||||
var legacyMockData = Data{
|
||||
Entries: []*logrus.Entry{
|
||||
|
@ -83,6 +83,30 @@ updt1 (mock/updt1:latest): Updated
|
|||
})
|
||||
})
|
||||
|
||||
When("adding a log hook", func() {
|
||||
When("it has not been added before", func() {
|
||||
It("should be added to the logrus hooks", func() {
|
||||
level := logrus.TraceLevel
|
||||
hooksBefore := len(logrus.StandardLogger().Hooks[level])
|
||||
shoutrrr := createNotifier([]string{}, level, "", true, StaticData{}, false, time.Second)
|
||||
shoutrrr.AddLogHook()
|
||||
hooksAfter := len(logrus.StandardLogger().Hooks[level])
|
||||
Expect(hooksAfter).To(BeNumerically(">", hooksBefore))
|
||||
})
|
||||
})
|
||||
When("it is being added a second time", func() {
|
||||
It("should not be added to the logrus hooks", func() {
|
||||
level := logrus.TraceLevel
|
||||
shoutrrr := createNotifier([]string{}, level, "", true, StaticData{}, false, time.Second)
|
||||
shoutrrr.AddLogHook()
|
||||
hooksBefore := len(logrus.StandardLogger().Hooks[level])
|
||||
shoutrrr.AddLogHook()
|
||||
hooksAfter := len(logrus.StandardLogger().Hooks[level])
|
||||
Expect(hooksAfter).To(Equal(hooksBefore))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("using legacy templates", func() {
|
||||
|
||||
When("no custom template is provided", func() {
|
||||
|
@ -90,7 +114,7 @@ updt1 (mock/updt1:latest): Updated
|
|||
cmd := new(cobra.Command)
|
||||
flags.RegisterNotificationFlags(cmd)
|
||||
|
||||
shoutrrr := createNotifier([]string{}, logrus.AllLevels, "", true, StaticData{}, false)
|
||||
shoutrrr := createNotifier([]string{}, logrus.TraceLevel, "", true, StaticData{}, false, time.Second)
|
||||
|
||||
entries := []*logrus.Entry{
|
||||
{
|
||||
|
@ -245,7 +269,7 @@ Turns out everything is on fire
|
|||
When("batching notifications", func() {
|
||||
When("no messages are queued", func() {
|
||||
It("should not send any notification", func() {
|
||||
shoutrrr := newShoutrrrNotifier("", allButTrace, true, StaticData{}, time.Duration(0), false, "logger://")
|
||||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{}, false, time.Duration(0))
|
||||
shoutrrr.StartNotification()
|
||||
shoutrrr.SendNotification(nil)
|
||||
Consistently(logBuffer).ShouldNot(gbytes.Say(`Shoutrrr:`))
|
||||
|
@ -253,7 +277,8 @@ Turns out everything is on fire
|
|||
})
|
||||
When("at least one message is queued", func() {
|
||||
It("should send a notification", func() {
|
||||
shoutrrr := newShoutrrrNotifier("", allButTrace, true, StaticData{}, time.Duration(0), false, "logger://")
|
||||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{}, false, time.Duration(0))
|
||||
shoutrrr.AddLogHook()
|
||||
shoutrrr.StartNotification()
|
||||
logrus.Info("This log message is sponsored by ContainrrrVPN")
|
||||
shoutrrr.SendNotification(nil)
|
||||
|
@ -267,7 +292,7 @@ Turns out everything is on fire
|
|||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{
|
||||
Host: "test.host",
|
||||
Title: "",
|
||||
}, false)
|
||||
}, false, time.Second)
|
||||
_, found := shoutrrr.params.Title()
|
||||
Expect(found).ToNot(BeTrue())
|
||||
})
|
||||
|
@ -321,13 +346,14 @@ func sendNotificationsWithBlockingRouter(legacy bool) (*shoutrrrTypeNotifier, *b
|
|||
Router: router,
|
||||
legacyTemplate: legacy,
|
||||
params: &types.Params{},
|
||||
delay: time.Duration(0),
|
||||
}
|
||||
|
||||
entry := &logrus.Entry{
|
||||
Message: "foo bar",
|
||||
}
|
||||
|
||||
go sendNotifications(shoutrrr, time.Duration(0))
|
||||
go sendNotifications(shoutrrr)
|
||||
|
||||
shoutrrr.StartNotification()
|
||||
_ = shoutrrr.Fire(entry)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
shoutrrrDisco "github.com/containrrr/shoutrrr/pkg/services/discord"
|
||||
shoutrrrSlack "github.com/containrrr/shoutrrr/pkg/services/slack"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/johntdyer/slackrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -16,11 +15,15 @@ const (
|
|||
)
|
||||
|
||||
type slackTypeNotifier struct {
|
||||
slackrus.SlackrusHook
|
||||
HookURL string
|
||||
Username string
|
||||
Channel string
|
||||
IconEmoji string
|
||||
IconURL string
|
||||
}
|
||||
|
||||
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newSlackNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
hookURL, _ := flags.GetString("notification-slack-hook-url")
|
||||
userName, _ := flags.GetString("notification-slack-identifier")
|
||||
|
@ -29,19 +32,16 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
iconURL, _ := flags.GetString("notification-slack-icon-url")
|
||||
|
||||
n := &slackTypeNotifier{
|
||||
SlackrusHook: slackrus.SlackrusHook{
|
||||
HookURL: hookURL,
|
||||
Username: userName,
|
||||
Channel: channel,
|
||||
IconEmoji: emoji,
|
||||
IconURL: iconURL,
|
||||
AcceptedLevels: acceptedLogLevels,
|
||||
},
|
||||
HookURL: hookURL,
|
||||
Username: userName,
|
||||
Channel: channel,
|
||||
IconEmoji: emoji,
|
||||
IconURL: iconURL,
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
trimmedURL := strings.TrimRight(s.HookURL, "/")
|
||||
trimmedURL = strings.TrimPrefix(trimmedURL, "https://")
|
||||
parts := strings.Split(trimmedURL, "/")
|
||||
|
@ -52,7 +52,6 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
WebhookID: parts[len(parts)-3],
|
||||
Token: parts[len(parts)-2],
|
||||
Color: ColorInt,
|
||||
Title: title,
|
||||
SplitLines: true,
|
||||
Username: s.Username,
|
||||
}
|
||||
|
@ -70,7 +69,6 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
BotName: s.Username,
|
||||
Color: ColorHex,
|
||||
Channel: "webhook",
|
||||
Title: title,
|
||||
}
|
||||
|
||||
if s.IconURL != "" {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ConvertibleNotifier is a notifier capable of creating a shoutrrr URL
|
||||
type ConvertibleNotifier interface {
|
||||
GetURL(c *cobra.Command, title string) (string, error)
|
||||
GetURL(c *cobra.Command) (string, error)
|
||||
}
|
||||
|
||||
// DelayNotifier is a notifier that might need to be delayed before sending notifications
|
||||
type DelayNotifier interface {
|
||||
GetDelay() time.Duration
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ package types
|
|||
type Notifier interface {
|
||||
StartNotification()
|
||||
SendNotification(Report)
|
||||
AddLogHook()
|
||||
GetNames() []string
|
||||
GetURLs() []string
|
||||
Close()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue