Skip to content

Commit

Permalink
Fix notifications and old instance cleanup (#748)
Browse files Browse the repository at this point in the history
Co-authored-by: Simon Aronsson <[email protected]>
  • Loading branch information
piksel and simskij committed Jan 10, 2021
1 parent 06e705d commit 40ab6fd
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 53 deletions.
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/containrrr/watchtower

go 1.12

replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a

require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.12 // indirect
Expand All @@ -15,7 +17,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-20200601144753-78bb9685bc2f
github.com/containrrr/shoutrrr v0.3.0
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
Expand Down Expand Up @@ -49,15 +51,14 @@ require (
github.com/prometheus/client_golang v0.9.3
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/spf13/cobra v0.0.7
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.6.3
github.com/stretchr/testify v1.3.0
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/zmap/zlint v1.0.2 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69 // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
Expand Down
57 changes: 57 additions & 0 deletions go.sum

Large diffs are not rendered by default.

28 changes: 5 additions & 23 deletions internal/actions/check.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package actions

import (
"errors"
"fmt"
"sort"
"strings"
"time"

"github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/sorter"

"github.com/sirupsen/logrus"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -40,7 +37,6 @@ func CheckForMultipleWatchtowerInstances(client container.Client, cleanup bool,
}

func cleanupExcessWatchtowers(containers []container.Container, client container.Client, cleanup bool) error {
var cleanupErrors int
var stopErrors int

sort.Sort(sorter.ByCreated(containers))
Expand All @@ -49,37 +45,23 @@ func cleanupExcessWatchtowers(containers []container.Container, client container
for _, c := range allContainersExceptLast {
if err := client.StopContainer(c, 10*time.Minute); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
logrus.WithError(err).Error("Could not stop a previous watchtower instance.")
stopErrors++
continue
}

if cleanup {
if err := client.RemoveImageByID(c.ImageID()); err != nil {
// logging the original here as we're just returning a count
logrus.Error(err)
cleanupErrors++
logrus.WithError(err).Warning("Could not cleanup watchtower images, possibly because of other watchtowers instances in other scopes.")
}
}
}

return createErrorIfAnyHaveOccurred(stopErrors, cleanupErrors)
}

func createErrorIfAnyHaveOccurred(c int, i int) error {
if c == 0 && i == 0 {
return nil
if stopErrors > 0 {
return fmt.Errorf("%d errors while stopping watchtower containers", stopErrors)
}

var output strings.Builder

if c > 0 {
output.WriteString(fmt.Sprintf("%d errors while stopping containers", c))
}
if i > 0 {
output.WriteString(fmt.Sprintf("%d errors while cleaning up images", c))
}
return errors.New(output.String())
return nil
}

func awaitDockerClient() {
Expand Down
5 changes: 2 additions & 3 deletions pkg/api/metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var _ = Describe("the metrics", func() {

Expect(err).NotTo(HaveOccurred())
contents, err := ioutil.ReadAll(res.Body)
fmt.Printf("%s\n", string(contents))

Expect(string(contents)).To(ContainSubstring("watchtower_containers_updated 3"))
Expect(string(contents)).To(ContainSubstring("watchtower_containers_failed 1"))
Expect(string(contents)).To(ContainSubstring("watchtower_containers_scanned 4"))
Expand All @@ -69,9 +69,8 @@ var _ = Describe("the metrics", func() {
res, err = getWithToken(c, "http://localhost:8080/v1/metrics")
Expect(err).NotTo(HaveOccurred())
contents, err = ioutil.ReadAll(res.Body)
fmt.Printf("%s\n", string(contents))

Expect(string(contents)).To(ContainSubstring("watchtower_scans_total 4"))
Expect(string(contents)).To(ContainSubstring("watchtower_scans_skipped 3"))
})
})
})
12 changes: 10 additions & 2 deletions pkg/notifications/email.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package notifications

import (
"fmt"
"os"
"time"

"github.com/containrrr/shoutrrr/pkg/format"
"github.com/spf13/cobra"

shoutrrrSmtp "github.com/containrrr/shoutrrr/pkg/services/smtp"
Expand Down Expand Up @@ -75,10 +77,16 @@ func (e *emailTypeNotifier) GetURL() string {
UseHTML: false,
}

pkr := format.NewPropKeyResolver(conf)
var err error
if len(e.User) > 0 {
conf.Set("auth", "Plain")
err = pkr.Set("auth", "Plain")
} else {
conf.Set("auth", "None")
err = pkr.Set("auth", "None")
}

if err != nil {
fmt.Printf("Could not set auth type for email notifier: %v", err)
}

return conf.GetURL().String()
Expand Down
42 changes: 27 additions & 15 deletions pkg/notifications/notifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,32 @@ func TestActions(t *testing.T) {
}

var _ = Describe("notifications", func() {
// TODO: Either, we delete this test or we need to pass it valid URLs in the cobra command.
// ---
// When("getting notifiers from a types array", func() {
// It("should return the same amount of notifiers a string entries", func() {

// notifier := &notifications.Notifier{}
// notifiers := notifier.GetNotificationTypes(&cobra.Command{}, []log.Level{}, []string{"slack", "email"})
// Expect(len(notifiers)).To(Equal(2))
// })
// })
Describe("the slack notifier", func() {
builderFn := notifications.NewSlackNotifier

When("passing a discord url to the slack notifier", func() {
channel := "123456789"
token := "abvsihdbau"
expected := fmt.Sprintf("discord://%s@%s", token, channel)
buildArgs := func(url string) []string {
return []string{
"--notifications",
"slack",
"--notification-slack-hook-url",
url,
}
}

It("should return a discord url when using a hook url with the domain discord.com", func() {
hookURL := fmt.Sprintf("https://%s/api/webhooks/%s/%s/slack", "discord.com", channel, token)
testURL(builderFn, buildArgs(hookURL), expected)
})
It("should return a discord url when using a hook url with the domain discordapp.com", func() {
hookURL := fmt.Sprintf("https://%s/api/webhooks/%s/%s/slack", "discordapp.com", channel, token)
testURL(builderFn, buildArgs(hookURL), expected)
})
})
When("converting a slack service config into a shoutrrr url", func() {
builderFn := notifications.NewSlackNotifier

It("should return the expected URL", func() {

Expand All @@ -43,9 +56,8 @@ var _ = Describe("notifications", func() {
tokenB := "bbb"
tokenC := "ccc"

password := fmt.Sprintf("%s-%s-%s", tokenA, tokenB, tokenC)
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
expectedOutput := fmt.Sprintf("slack://%s:%s@%s/%s/%s", username, password, tokenA, tokenB, tokenC)
expectedOutput := fmt.Sprintf("slack://%s@%s/%s/%s", username, tokenA, tokenB, tokenC)

args := []string{
"--notification-slack-hook-url",
Expand Down Expand Up @@ -144,8 +156,8 @@ func buildExpectedURL(username string, password string, host string, port int, f

subject := fmt.Sprintf("Watchtower updates on %s", hostname)

var template = "smtp://%s:%s@%s:%d/?fromAddress=%s&fromName=Watchtower&toAddresses=%s&auth=%s&subject=%s&startTls=Yes&useHTML=No"
return fmt.Sprintf(template, username, password, host, port, from, to, auth, subject)
var template = "smtp://%s:%s@%s:%d/?auth=%s&encryption=None&fromaddress=%s&fromname=Watchtower&starttls=Yes&subject=%s&toaddresses=%s&usehtml=No"
return fmt.Sprintf(template, username, password, host, port, auth, from, subject, to)
}

type builderFn = func(c *cobra.Command, acceptedLogLevels []log.Level) types.ConvertableNotifier
Expand Down
21 changes: 16 additions & 5 deletions pkg/notifications/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package notifications
import (
"strings"

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"
Expand Down Expand Up @@ -31,6 +32,7 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
channel, _ := flags.GetString("notification-slack-channel")
emoji, _ := flags.GetString("notification-slack-icon-emoji")
iconURL, _ := flags.GetString("notification-slack-icon-url")

n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{
HookURL: hookURL,
Expand All @@ -45,16 +47,25 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
}

func (s *slackTypeNotifier) GetURL() string {
trimmedURL := strings.TrimRight(s.HookURL, "/")
trimmedURL = strings.TrimLeft(trimmedURL, "https://")
parts := strings.Split(trimmedURL, "/")

if parts[0] == "discord.com" || parts[0] == "discordapp.com" {
log.Debug("Detected a discord slack wrapper URL, using shoutrrr discord service")
conf := &shoutrrrDisco.Config{
Channel: parts[len(parts)-3],
Token: parts[len(parts)-2],
}
return conf.GetURL().String()
}

rawTokens := strings.Replace(s.HookURL, "https://hooks.slack.com/services/", "", 1)
tokens := strings.Split(rawTokens, "/")

conf := &shoutrrrSlack.Config{
BotName: s.Username,
Token: shoutrrrSlack.Token{
A: tokens[0],
B: tokens[1],
C: tokens[2],
},
Token: tokens,
}

return conf.GetURL().String()
Expand Down

0 comments on commit 40ab6fd

Please sign in to comment.