Repository: containrrr/watchtower
Pull request #994 (2021-06-18 05:28:13)

feat: allow hostname override for notifiers
As it currently stands all notifiers utilise `os.Hostname` to populate their titles/subjects.

When utilising Docker with a bridged network if you set the hostname for a container to an external DNS hostname Docker's internal DNS resolver will override said hostname for all containers within the bridged network.

This change allows a user to specify what hostname should be represented in the notifications without having to change the `os.Hostname`.

codecov[bot]: # [Codecov](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr) Report
> Merging [#994](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr) (5b52b07) into [main](https://codecov.io/gh/containrrr/watchtower/commit/f508c92ae0b0188f71b0b5dbff628cb52f7f10fe?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr) (f508c92) will **increase** coverage by `0.09%`.
> The diff coverage is `89.28%`.

[![Impacted file tree graph](https://codecov.io/gh/containrrr/watchtower/pull/994/graphs/tree.svg?width=650&height=150&src=pr&token=8pxWgB380Y&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr)](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr)

```diff
@@            Coverage Diff             @@
##             main     #994      +/-   ##
==========================================
+ Coverage   56.15%   56.25%   +0.09%     
==========================================
  Files          25       25              
  Lines        1453     1463      +10     
==========================================
+ Hits          816      823       +7     
- Misses        567      568       +1     
- Partials       70       72       +2     
```


| [Impacted Files](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr) | Coverage Δ | |
|---|---|---|
| [pkg/notifications/notifier.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-cGtnL25vdGlmaWNhdGlvbnMvbm90aWZpZXIuZ28=) | `31.74% <40.00%> (-1.59%)` | :arrow_down: |
| [internal/flags/flags.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-aW50ZXJuYWwvZmxhZ3MvZmxhZ3MuZ28=) | `88.65% <100.00%> (+0.17%)` | :arrow_up: |
| [pkg/notifications/email.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-cGtnL25vdGlmaWNhdGlvbnMvZW1haWwuZ28=) | `91.30% <100.00%> (+0.39%)` | :arrow_up: |
| [pkg/notifications/gotify.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-cGtnL25vdGlmaWNhdGlvbnMvZ290aWZ5Lmdv) | `67.74% <100.00%> (ø)` | |
| [pkg/notifications/msteams.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-cGtnL25vdGlmaWNhdGlvbnMvbXN0ZWFtcy5nbw==) | `68.42% <100.00%> (ø)` | |
| [pkg/notifications/slack.go](https://codecov.io/gh/containrrr/watchtower/pull/994/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr#diff-cGtnL25vdGlmaWNhdGlvbnMvc2xhY2suZ28=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr). Last update [f508c92...5b52b07](https://codecov.io/gh/containrrr/watchtower/pull/994?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=containrrr).


Patch:
diff --git a/docs/notifications.md b/docs/notifications.md
index b0e2adf..f783866 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -25,6 +25,7 @@ comma-separated list of values to the `--notifications` option
 ## Settings
 
 - `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info`, `debug` or `trace`.
+- `--notifications-hostname` (env. `WATCHTOWER_NOTIFICATIONS_HOSTNAME`): Custom hostname specified in subject/title. Useful to override the operating system hostname.
 - Watchtower will post a notification every time it is started. This behavior [can be changed](https://containrrr.github.io/watchtower/arguments/#without_sending_a_startup_message) with an argument.
 
 ## Available services
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 177073d..9df42c1 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -185,6 +185,12 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
 		viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
 		"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
 
+	flags.StringP(
+		"notifications-hostname",
+		"",
+		viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
+		"Custom hostname for notification titles")
+
 	flags.StringP(
 		"notification-email-from",
 		"",
diff --git a/pkg/notifications/email.go b/pkg/notifications/email.go
index 4984139..e26ca97 100644
--- a/pkg/notifications/email.go
+++ b/pkg/notifications/email.go
@@ -60,14 +60,14 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
 	return n
 }
 
-func (e *emailTypeNotifier) GetURL() (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(),
+		Subject:     e.getSubject(c),
 		Username:    e.User,
 		Password:    e.Password,
 		UseStartTLS: !e.tlsSkipVerify,
@@ -87,8 +87,8 @@ func (e *emailTypeNotifier) GetURL() (string, error) {
 	return conf.GetURL().String(), nil
 }
 
-func (e *emailTypeNotifier) getSubject() string {
-	subject := GetTitle()
+func (e *emailTypeNotifier) getSubject(c *cobra.Command) string {
+	subject := GetTitle(c)
 
 	if e.SubjectTag != "" {
 		subject = e.SubjectTag + " " + subject
diff --git a/pkg/notifications/gotify.go b/pkg/notifications/gotify.go
index 7a6009b..85f59b1 100644
--- a/pkg/notifications/gotify.go
+++ b/pkg/notifications/gotify.go
@@ -67,7 +67,7 @@ func getGotifyURL(flags *pflag.FlagSet) string {
 	return gotifyURL
 }
 
-func (n *gotifyTypeNotifier) GetURL() (string, error) {
+func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) {
 	apiURL, err := url.Parse(n.gotifyURL)
 	if err != nil {
 		return "", err
@@ -77,7 +77,7 @@ func (n *gotifyTypeNotifier) GetURL() (string, error) {
 		Host:       apiURL.Host,
 		Path:       apiURL.Path,
 		DisableTLS: apiURL.Scheme == "http",
-		Title:      GetTitle(),
+		Title:      GetTitle(c),
 		Token:      n.gotifyAppToken,
 	}
 
diff --git a/pkg/notifications/msteams.go b/pkg/notifications/msteams.go
index 6c47229..282ce05 100644
--- a/pkg/notifications/msteams.go
+++ b/pkg/notifications/msteams.go
@@ -42,7 +42,7 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
 	return n
 }
 
-func (n *msTeamsTypeNotifier) GetURL() (string, error) {
+func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) {
 	webhookURL, err := url.Parse(n.webHookURL)
 	if err != nil {
 		return "", err
@@ -54,7 +54,7 @@ func (n *msTeamsTypeNotifier) GetURL() (string, error) {
 	}
 
 	config.Color = ColorHex
-	config.Title = GetTitle()
+	config.Title = GetTitle(c)
 
 	return config.GetURL().String(), nil
 }
diff --git a/pkg/notifications/notifier.go b/pkg/notifications/notifier.go
index c4e962f..358c5f3 100644
--- a/pkg/notifications/notifier.go
+++ b/pkg/notifications/notifier.go
@@ -98,7 +98,7 @@ func (n *Notifier) getNotificationTypes(cmd *cobra.Command, levels []log.Level,
 			continue
 		}
 
-		shoutrrrURL, err := legacyNotifier.GetURL()
+		shoutrrrURL, err := legacyNotifier.GetURL(cmd)
 		if err != nil {
 			log.Fatal("failed to create notification config:", err)
 		}
@@ -139,10 +139,16 @@ func (n *Notifier) Close() {
 }
 
 // GetTitle returns a common notification title with hostname appended
-func GetTitle() (title string) {
+func GetTitle(c *cobra.Command) (title string) {
 	title = "Watchtower updates"
 
-	if hostname, err := os.Hostname(); err == nil {
+	f := c.PersistentFlags()
+
+	hostname, _ := f.GetString("notifications-hostname")
+
+	if hostname != "" {
+		title += " on " + hostname
+	} else if hostname, err := os.Hostname(); err == nil {
 		title += " on " + hostname
 	}
 
diff --git a/pkg/notifications/slack.go b/pkg/notifications/slack.go
index b3df119..63cb44c 100644
--- a/pkg/notifications/slack.go
+++ b/pkg/notifications/slack.go
@@ -46,7 +46,7 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
 	return n
 }
 
-func (s *slackTypeNotifier) GetURL() (string, error) {
+func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
 	trimmedURL := strings.TrimRight(s.HookURL, "/")
 	trimmedURL = strings.TrimLeft(trimmedURL, "https://")
 	parts := strings.Split(trimmedURL, "/")
@@ -57,7 +57,7 @@ func (s *slackTypeNotifier) GetURL() (string, error) {
 			Channel:    parts[len(parts)-3],
 			Token:      parts[len(parts)-2],
 			Color:      ColorInt,
-			Title:      GetTitle(),
+			Title:      GetTitle(c),
 			SplitLines: true,
 			Username:   s.Username,
 		}
@@ -71,7 +71,7 @@ func (s *slackTypeNotifier) GetURL() (string, error) {
 		BotName: s.Username,
 		Token:   tokens,
 		Color:   ColorHex,
-		Title:   GetTitle(),
+		Title:   GetTitle(c),
 	}
 
 	return conf.GetURL().String(), nil
diff --git a/pkg/types/convertible_notifier.go b/pkg/types/convertible_notifier.go
index 2614d12..87f8659 100644
--- a/pkg/types/convertible_notifier.go
+++ b/pkg/types/convertible_notifier.go
@@ -1,6 +1,8 @@
 package types
 
+import "github.com/spf13/cobra"
+
 // ConvertibleNotifier is a notifier capable of creating a shoutrrr URL
 type ConvertibleNotifier interface {
-	GetURL() (string, error)
+	GetURL(c *cobra.Command) (string, error)
 }


Test patch:
diff --git a/pkg/notifications/notifier_test.go b/pkg/notifications/notifier_test.go
index ba6657a..f95ecbc 100644
--- a/pkg/notifications/notifier_test.go
+++ b/pkg/notifications/notifier_test.go
@@ -43,10 +43,13 @@ var _ = Describe("notifications", func() {
 		builderFn := notifications.NewSlackNotifier
 
 		When("passing a discord url to the slack notifier", func() {
+			command := cmd.NewRootCommand()
+			flags.RegisterNotificationFlags(command)
+
 			channel := "123456789"
 			token := "abvsihdbau"
 			color := notifications.ColorInt
-			title := url.QueryEscape(notifications.GetTitle())
+			title := url.QueryEscape(notifications.GetTitle(command))
 			expected := fmt.Sprintf("discord://%s@%s?color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&splitlines=Yes&title=%s&username=watchtower", token, channel, color, title)
 			buildArgs := func(url string) []string {
 				return []string{
@@ -69,13 +72,15 @@ var _ = Describe("notifications", func() {
 		When("converting a slack service config into a shoutrrr url", func() {
 
 			It("should return the expected URL", func() {
+				command := cmd.NewRootCommand()
+				flags.RegisterNotificationFlags(command)
 
 				username := "containrrrbot"
 				tokenA := "aaa"
 				tokenB := "bbb"
 				tokenC := "ccc"
 				color := url.QueryEscape(notifications.ColorHex)
-				title := url.QueryEscape(notifications.GetTitle())
+				title := url.QueryEscape(notifications.GetTitle(command))
 
 				hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
 				expectedOutput := fmt.Sprintf("slack://%s@%s/%s/%s?color=%s&title=%s", username, tokenA, tokenB, tokenC, color, title)
@@ -97,9 +102,12 @@ var _ = Describe("notifications", func() {
 			builderFn := notifications.NewGotifyNotifier
 
 			It("should return the expected URL", func() {
+				command := cmd.NewRootCommand()
+				flags.RegisterNotificationFlags(command)
+
 				token := "aaa"
 				host := "shoutrrr.local"
-				title := url.QueryEscape(notifications.GetTitle())
+				title := url.QueryEscape(notifications.GetTitle(command))
 
 				expectedOutput := fmt.Sprintf("gotify://%s/%s?title=%s", host, token, title)
 
@@ -120,12 +128,14 @@ var _ = Describe("notifications", func() {
 			builderFn := notifications.NewMsTeamsNotifier
 
 			It("should return the expected URL", func() {
+				command := cmd.NewRootCommand()
+				flags.RegisterNotificationFlags(command)
 
 				tokenA := "11111111-4444-4444-8444-cccccccccccc@22222222-4444-4444-8444-cccccccccccc"
 				tokenB := "33333333012222222222333333333344"
 				tokenC := "44444444-4444-4444-8444-cccccccccccc"
 				color := url.QueryEscape(notifications.ColorHex)
-				title := url.QueryEscape(notifications.GetTitle())
+				title := url.QueryEscape(notifications.GetTitle(command))
 
 				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)
@@ -215,7 +225,7 @@ func testURL(builder builderFn, args []string, expectedURL string) {
 	Expect(err).NotTo(HaveOccurred())
 
 	notifier := builder(command, []log.Level{})
-	actualURL, err := notifier.GetURL()
+	actualURL, err := notifier.GetURL(command)
 
 	Expect(err).NotTo(HaveOccurred())