Revert "feat(config): swap viper and cobra for config (#684)"

This reverts commit ff8cb884a0.
pull/716/head v1.1.3
Simon Aronsson 4 years ago
parent 89119515af
commit 8b81fbd48d
No known key found for this signature in database
GPG Key ID: 8DA57A5FD341605B

@ -1,10 +1,9 @@
package cmd package cmd
import ( import (
"fmt"
"github.com/spf13/viper"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"time" "time"
@ -22,9 +21,17 @@ import (
) )
var ( var (
client container.Client client container.Client
notifier *notifications.Notifier scheduleSpec string
c flags.WatchConfig cleanup bool
noRestart bool
monitorOnly bool
enableLabel bool
notifier *notifications.Notifier
timeout time.Duration
lifecycleHooks bool
rollingRestart bool
scope string
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -39,11 +46,10 @@ More information available at https://github.com/containrrr/watchtower/.
} }
func init() { func init() {
flags.SetDefaults()
flags.RegisterDockerFlags(rootCmd) flags.RegisterDockerFlags(rootCmd)
flags.RegisterSystemFlags(rootCmd) flags.RegisterSystemFlags(rootCmd)
flags.RegisterNotificationFlags(rootCmd) flags.RegisterNotificationFlags(rootCmd)
flags.SetEnvBindings()
flags.BindViperFlags(rootCmd)
} }
// Execute the root func and exit in case of errors // Execute the root func and exit in case of errors
@ -54,10 +60,10 @@ func Execute() {
} }
// PreRun is a lifecycle hook that runs before the command is executed. // PreRun is a lifecycle hook that runs before the command is executed.
func PreRun(cmd *cobra.Command, _ []string) { func PreRun(cmd *cobra.Command, args []string) {
f := cmd.PersistentFlags()
// First apply all the settings that affect the output if enabled, _ := f.GetBool("no-color"); enabled {
if viper.GetBool("no-color") {
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
DisableColors: true, DisableColors: true,
}) })
@ -68,55 +74,75 @@ func PreRun(cmd *cobra.Command, _ []string) {
}) })
} }
if viper.GetBool("debug") { if enabled, _ := f.GetBool("debug"); enabled {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }
if viper.GetBool("trace") { if enabled, _ := f.GetBool("trace"); enabled {
log.SetLevel(log.TraceLevel) log.SetLevel(log.TraceLevel)
} }
interval := viper.GetInt("interval") pollingSet := f.Changed("interval")
schedule, _ := f.GetString("schedule")
cronLen := len(schedule)
// If empty, set schedule using interval helper value if pollingSet && cronLen > 0 {
if viper.GetString("schedule") == "" { log.Fatal("Only schedule or interval can be defined, not both.")
viper.Set("schedule", fmt.Sprintf("@every %ds", interval)) } else if cronLen > 0 {
} else if interval != flags.DefaultInterval { scheduleSpec, _ = f.GetString("schedule")
log.Fatal("only schedule or interval can be defined, not both") } else {
} interval, _ := f.GetInt("interval")
scheduleSpec = "@every " + strconv.Itoa(interval) + "s"
// Then load the rest of the settings
err := viper.Unmarshal(&c)
if err != nil {
log.Fatalf("unable to decode into struct, %v", err)
} }
flags.GetSecretsFromFiles() flags.GetSecretsFromFiles(cmd)
cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd)
if c.Timeout <= 0 { if timeout < 0 {
log.Fatal("Please specify a positive value for timeout value.") log.Fatal("Please specify a positive value for timeout value.")
} }
log.Debugf("Using scope %v", c.Scope) enableLabel, _ = f.GetBool("label-enable")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
log.Debug(scope)
if err = flags.EnvConfig(); err != nil { // configure environment vars for client
log.Fatalf("failed to setup environment variables: %v", err) err := flags.EnvConfig(cmd)
if err != nil {
log.Fatal(err)
} }
if c.MonitorOnly && c.NoPull { noPull, _ := f.GetBool("no-pull")
includeStopped, _ := f.GetBool("include-stopped")
includeRestarting, _ := f.GetBool("include-restarting")
reviveStopped, _ := f.GetBool("revive-stopped")
removeVolumes, _ := f.GetBool("remove-volumes")
if monitorOnly && noPull {
log.Warn("Using `WATCHTOWER_NO_PULL` and `WATCHTOWER_MONITOR_ONLY` simultaneously might lead to no action being taken at all. If this is intentional, you may safely ignore this message.") log.Warn("Using `WATCHTOWER_NO_PULL` and `WATCHTOWER_MONITOR_ONLY` simultaneously might lead to no action being taken at all. If this is intentional, you may safely ignore this message.")
} }
client = container.NewClient(&c) client = container.NewClient(
!noPull,
includeStopped,
reviveStopped,
removeVolumes,
includeRestarting,
)
notifier = notifications.NewNotifier(cmd) notifier = notifications.NewNotifier(cmd)
} }
// Run is the main execution flow of the command // Run is the main execution flow of the command
func Run(_ *cobra.Command, names []string) { func Run(c *cobra.Command, names []string) {
filter := filters.BuildFilter(names, c.EnableLabel, c.Scope) filter := filters.BuildFilter(names, enableLabel, scope)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
httpAPI, _ := c.PersistentFlags().GetBool("http-api")
if c.RunOnce { if runOnce {
if !c.NoStartupMessage { if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
log.Info("Running a one time update.") log.Info("Running a one time update.")
} }
runUpdatesWithNotifications(filter) runUpdatesWithNotifications(filter)
@ -125,12 +151,14 @@ func Run(_ *cobra.Command, names []string) {
return return
} }
if err := actions.CheckForMultipleWatchtowerInstances(client, c.Cleanup, c.Scope); err != nil { if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup, scope); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if c.HTTPAPI { if httpAPI {
if err := api.SetupHTTPUpdates(c.HTTPAPIToken, func() { runUpdatesWithNotifications(filter) }); err != nil { apiToken, _ := c.PersistentFlags().GetString("http-api-token")
if err := api.SetupHTTPUpdates(apiToken, func() { runUpdatesWithNotifications(filter) }); err != nil {
log.Fatal(err) log.Fatal(err)
os.Exit(1) os.Exit(1)
} }
@ -138,20 +166,20 @@ func Run(_ *cobra.Command, names []string) {
api.WaitForHTTPUpdates() api.WaitForHTTPUpdates()
} }
if err := runUpgradesOnSchedule(filter); err != nil { if err := runUpgradesOnSchedule(c, filter); err != nil {
log.Error(err) log.Error(err)
} }
os.Exit(1) os.Exit(1)
} }
func runUpgradesOnSchedule(filter t.Filter) error { func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter) error {
tryLockSem := make(chan bool, 1) tryLockSem := make(chan bool, 1)
tryLockSem <- true tryLockSem <- true
runner := cron.New() cron := cron.New()
err := runner.AddFunc( err := cron.AddFunc(
viper.GetString("schedule"), scheduleSpec,
func() { func() {
select { select {
case v := <-tryLockSem: case v := <-tryLockSem:
@ -161,7 +189,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
log.Debug("Skipped another update already running.") log.Debug("Skipped another update already running.")
} }
nextRuns := runner.Entries() nextRuns := cron.Entries()
if len(nextRuns) > 0 { if len(nextRuns) > 0 {
log.Debug("Scheduled next run: " + nextRuns[0].Next.String()) log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
} }
@ -171,11 +199,11 @@ func runUpgradesOnSchedule(filter t.Filter) error {
return err return err
} }
if !viper.GetBool("no-startup-message") { if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
log.Info("Starting Watchtower and scheduling first run: " + runner.Entries()[0].Schedule.Next(time.Now()).String()) log.Info("Starting Watchtower and scheduling first run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
} }
runner.Start() cron.Start()
// Graceful shut-down on SIGINT/SIGTERM // Graceful shut-down on SIGINT/SIGTERM
interrupt := make(chan os.Signal, 1) interrupt := make(chan os.Signal, 1)
@ -183,7 +211,7 @@ func runUpgradesOnSchedule(filter t.Filter) error {
signal.Notify(interrupt, syscall.SIGTERM) signal.Notify(interrupt, syscall.SIGTERM)
<-interrupt <-interrupt
runner.Stop() cron.Stop()
log.Info("Waiting for running update to be finished...") log.Info("Waiting for running update to be finished...")
<-tryLockSem <-tryLockSem
return nil return nil
@ -193,12 +221,12 @@ func runUpdatesWithNotifications(filter t.Filter) {
notifier.StartNotification() notifier.StartNotification()
updateParams := t.UpdateParams{ updateParams := t.UpdateParams{
Filter: filter, Filter: filter,
Cleanup: c.Cleanup, Cleanup: cleanup,
NoRestart: c.NoRestart, NoRestart: noRestart,
Timeout: c.Timeout, Timeout: timeout,
MonitorOnly: c.MonitorOnly, MonitorOnly: monitorOnly,
LifecycleHooks: c.LifecycleHooks, LifecycleHooks: lifecycleHooks,
RollingRestart: c.RollingRestart, RollingRestart: rollingRestart,
} }
err := actions.Update(client, updateParams) err := actions.Update(client, updateParams)
if err != nil { if err != nil {

@ -1,31 +0,0 @@
package flags
import (
"time"
)
// WatchConfig is the global watchtower configuration created from flags and environment variables
type WatchConfig struct {
Interval int
Schedule string
NoPull bool `mapstructure:"no-pull"`
NoRestart bool `mapstructure:"no-restart"`
NoStartupMessage bool `mapstructure:"no-startup-message"`
Cleanup bool
RemoveVolumes bool `mapstructure:"remove-volumes"`
EnableLabel bool `mapstructure:"label-enable"`
Debug bool
Trace bool
MonitorOnly bool `mapstructure:"monitor-only"`
RunOnce bool `mapstructure:"run-once"`
IncludeStopped bool `mapstructure:"include-stopped"`
IncludeRestarting bool `mapstructure:"include-restarting"`
ReviveStopped bool `mapstructure:"revive-stopped"`
LifecycleHooks bool `mapstructure:"enable-lifecycle-hooks"`
RollingRestart bool `mapstructure:"rolling-restart"`
HTTPAPI bool `mapstructure:"http-api"`
HTTPAPIToken string `mapstructure:"http-api-token"`
Timeout time.Duration `mapstructure:"stop-timeout"`
Scope string
NoColor bool `mapstructure:"no-color"`
}

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -15,15 +16,12 @@ import (
// use watchtower // use watchtower
const DockerAPIMinVersion string = "1.25" const DockerAPIMinVersion string = "1.25"
// DefaultInterval is the default time between the start of update checks
const DefaultInterval = int(time.Hour * 24 / time.Second)
// RegisterDockerFlags that are used directly by the docker api client // RegisterDockerFlags that are used directly by the docker api client
func RegisterDockerFlags(rootCmd *cobra.Command) { func RegisterDockerFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags() flags := rootCmd.PersistentFlags()
flags.StringP("host", "H", "unix:///var/run/docker.sock", "daemon socket to connect to") flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
flags.BoolP("tlsverify", "v", false, "use TLS and verify the remote") flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
flags.StringP("api-version", "a", DockerAPIMinVersion, "api version to use by docker client") flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client")
} }
// RegisterSystemFlags that are used by watchtower to modify the program flow // RegisterSystemFlags that are used by watchtower to modify the program flow
@ -32,126 +30,126 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
flags.IntP( flags.IntP(
"interval", "interval",
"i", "i",
DefaultInterval, viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
"poll interval (in seconds)") "poll interval (in seconds)")
flags.StringP( flags.StringP(
"schedule", "schedule",
"s", "s",
"", viper.GetString("WATCHTOWER_SCHEDULE"),
"the cron expression which defines when to update") "the cron expression which defines when to update")
flags.DurationP( flags.DurationP(
"stop-timeout", "stop-timeout",
"t", "t",
time.Second*10, viper.GetDuration("WATCHTOWER_TIMEOUT"),
"timeout before a container is forcefully stopped") "timeout before a container is forcefully stopped")
flags.BoolP( flags.BoolP(
"no-pull", "no-pull",
"", "",
false, viper.GetBool("WATCHTOWER_NO_PULL"),
"do not pull any new images") "do not pull any new images")
flags.BoolP( flags.BoolP(
"no-restart", "no-restart",
"", "",
false, viper.GetBool("WATCHTOWER_NO_RESTART"),
"do not restart any containers") "do not restart any containers")
flags.BoolP( flags.BoolP(
"no-startup-message", "no-startup-message",
"", "",
false, viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
"Prevents watchtower from sending a startup message") "Prevents watchtower from sending a startup message")
flags.BoolP( flags.BoolP(
"cleanup", "cleanup",
"c", "c",
false, viper.GetBool("WATCHTOWER_CLEANUP"),
"remove previously used images after updating") "remove previously used images after updating")
flags.BoolP( flags.BoolP(
"remove-volumes", "remove-volumes",
"", "",
false, viper.GetBool("WATCHTOWER_REMOVE_VOLUMES"),
"remove attached volumes before updating") "remove attached volumes before updating")
flags.BoolP( flags.BoolP(
"label-enable", "label-enable",
"e", "e",
false, viper.GetBool("WATCHTOWER_LABEL_ENABLE"),
"watch containers where the com.centurylinklabs.watchtower.enable label is true") "watch containers where the com.centurylinklabs.watchtower.enable label is true")
flags.BoolP( flags.BoolP(
"debug", "debug",
"d", "d",
false, viper.GetBool("WATCHTOWER_DEBUG"),
"enable debug mode with verbose logging") "enable debug mode with verbose logging")
flags.BoolP( flags.BoolP(
"trace", "trace",
"", "",
false, viper.GetBool("WATCHTOWER_TRACE"),
"enable trace mode with very verbose logging - caution, exposes credentials") "enable trace mode with very verbose logging - caution, exposes credentials")
flags.BoolP( flags.BoolP(
"monitor-only", "monitor-only",
"m", "m",
false, viper.GetBool("WATCHTOWER_MONITOR_ONLY"),
"Will only monitor for new images, not update the containers") "Will only monitor for new images, not update the containers")
flags.BoolP( flags.BoolP(
"run-once", "run-once",
"R", "R",
false, viper.GetBool("WATCHTOWER_RUN_ONCE"),
"Run once now and exit") "Run once now and exit")
flags.BoolP( flags.BoolP(
"include-stopped", "include-stopped",
"S", "S",
false, viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
"Will also include created and exited containers") "Will also include created and exited containers")
flags.BoolP( flags.BoolP(
"revive-stopped", "revive-stopped",
"", "",
false, viper.GetBool("WATCHTOWER_REVIVE_STOPPED"),
"Will also start stopped containers that were updated, if include-stopped is active") "Will also start stopped containers that were updated, if include-stopped is active")
flags.BoolP( flags.BoolP(
"enable-lifecycle-hooks", "enable-lifecycle-hooks",
"", "",
false, viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks") "Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
flags.BoolP( flags.BoolP(
"rolling-restart", "rolling-restart",
"", "",
false, viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
"Restart containers one at a time") "Restart containers one at a time")
flags.BoolP( flags.BoolP(
"http-api", "http-api",
"", "",
false, viper.GetBool("WATCHTOWER_HTTP_API"),
"Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request") "Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request")
flags.StringP( flags.StringP(
"http-api-token", "http-api-token",
"", "",
"", viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
"Sets an authentication token to HTTP API requests.") "Sets an authentication token to HTTP API requests.")
// https://no-color.org/ // https://no-color.org/
flags.BoolP( flags.BoolP(
"no-color", "no-color",
"", "",
false, viper.IsSet("NO_COLOR"),
"Disable ANSI color escape codes in log output") "Disable ANSI color escape codes in log output")
flags.StringP( flags.StringP(
"scope", "scope",
"", "",
"", viper.GetString("WATCHTOWER_SCOPE"),
"Defines a monitoring scope for the Watchtower instance.") "Defines a monitoring scope for the Watchtower instance.")
} }
@ -162,177 +160,178 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
flags.StringSliceP( flags.StringSliceP(
"notifications", "notifications",
"n", "n",
[]string{}, viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
" notification types to send (valid: email, slack, msteams, gotify, shoutrrr)") " notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
flags.StringP( flags.StringP(
"notifications-level", "notifications-level",
"", "",
"info", viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug") "The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
flags.StringP( flags.StringP(
"notification-email-from", "notification-email-from",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
"Address to send notification emails from") "Address to send notification emails from")
flags.StringP( flags.StringP(
"notification-email-to", "notification-email-to",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
"Address to send notification emails to") "Address to send notification emails to")
flags.IntP( flags.IntP(
"notification-email-delay", "notification-email-delay",
"", "",
0, viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
"Delay before sending notifications, expressed in seconds") "Delay before sending notifications, expressed in seconds")
flags.StringP( flags.StringP(
"notification-email-server", "notification-email-server",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
"SMTP server to send notification emails through") "SMTP server to send notification emails through")
flags.IntP( flags.IntP(
"notification-email-server-port", "notification-email-server-port",
"", "",
25, viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
"SMTP server port to send notification emails through") "SMTP server port to send notification emails through")
flags.BoolP( flags.BoolP(
"notification-email-server-tls-skip-verify", "notification-email-server-tls-skip-verify",
"", "",
false, viper.GetBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"),
`Controls whether watchtower verifies the SMTP server's certificate chain and host name. `Controls whether watchtower verifies the SMTP server's certificate chain and host name.
Should only be used for testing.`) Should only be used for testing.`)
flags.StringP( flags.StringP(
"notification-email-server-user", "notification-email-server-user",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
"SMTP server user for sending notifications") "SMTP server user for sending notifications")
flags.StringP( flags.StringP(
"notification-email-server-password", "notification-email-server-password",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
"SMTP server password for sending notifications") "SMTP server password for sending notifications")
flags.StringP( flags.StringP(
"notification-email-subjecttag", "notification-email-subjecttag",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
"Subject prefix tag for notifications via mail") "Subject prefix tag for notifications via mail")
flags.StringP( flags.StringP(
"notification-slack-hook-url", "notification-slack-hook-url",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"),
"The Slack Hook URL to send notifications to") "The Slack Hook URL to send notifications to")
flags.StringP( flags.StringP(
"notification-slack-identifier", "notification-slack-identifier",
"", "",
"watchtower", viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"),
"A string which will be used to identify the messages coming from this watchtower instance") "A string which will be used to identify the messages coming from this watchtower instance")
flags.StringP( flags.StringP(
"notification-slack-channel", "notification-slack-channel",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"),
"A string which overrides the webhook's default channel. Example: #my-custom-channel") "A string which overrides the webhook's default channel. Example: #my-custom-channel")
flags.StringP( flags.StringP(
"notification-slack-icon-emoji", "notification-slack-icon-emoji",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"),
"An emoji code string to use in place of the default icon") "An emoji code string to use in place of the default icon")
flags.StringP( flags.StringP(
"notification-slack-icon-url", "notification-slack-icon-url",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"),
"An icon image URL string to use in place of the default icon") "An icon image URL string to use in place of the default icon")
flags.StringP( flags.StringP(
"notification-msteams-hook", "notification-msteams-hook",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
"The MSTeams WebHook URL to send notifications to") "The MSTeams WebHook URL to send notifications to")
flags.BoolP( flags.BoolP(
"notification-msteams-data", "notification-msteams-data",
"", "",
false, viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts") "The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
flags.StringP( flags.StringP(
"notification-gotify-url", "notification-gotify-url",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
"The Gotify URL to send notifications to") "The Gotify URL to send notifications to")
flags.StringP( flags.StringP(
"notification-gotify-token", "notification-gotify-token",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
"The Gotify Application required to query the Gotify API") "The Gotify Application required to query the Gotify API")
flags.BoolP( flags.BoolP(
"notification-gotify-tls-skip-verify", "notification-gotify-tls-skip-verify",
"", "",
false, viper.GetBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"),
`Controls whether watchtower verifies the Gotify server's certificate chain and host name. `Controls whether watchtower verifies the Gotify server's certificate chain and host name.
Should only be used for testing.`) Should only be used for testing.`)
flags.StringP( flags.StringP(
"notification-template", "notification-template",
"", "",
"", viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
"The shoutrrr text/template for the messages") "The shoutrrr text/template for the messages")
flags.StringArrayP( flags.StringArrayP(
"notification-url", "notification-url",
"", "",
[]string{}, viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
"The shoutrrr URL to send notifications to") "The shoutrrr URL to send notifications to")
} }
// SetEnvBindings binds environment variables to their corresponding config keys // SetDefaults provides default values for environment variables
func SetEnvBindings() { func SetDefaults() {
if err := viper.BindEnv("host", "DOCKER_HOST"); err != nil { day := (time.Hour * 24).Seconds()
log.Fatalf("failed to bind env DOCKER_HOST: %v", err)
}
if err := viper.BindEnv("tlsverify", "DOCKER_TLS_VERIFY"); err != nil {
log.Fatalf("failed to bind env DOCKER_TLS_VERIFY: %v", err)
}
if err := viper.BindEnv("api-version", "DOCKER_API_VERSION"); err != nil {
log.Fatalf("failed to bind env DOCKER_API_VERSION: %v", err)
}
viper.SetEnvPrefix("WATCHTOWER")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv() viper.AutomaticEnv()
} viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
viper.SetDefault("DOCKER_API_VERSION", DockerAPIMinVersion)
// BindViperFlags binds the cmd PFlags to the viper configuration viper.SetDefault("WATCHTOWER_POLL_INTERVAL", day)
func BindViperFlags(cmd *cobra.Command) { viper.SetDefault("WATCHTOWER_TIMEOUT", time.Second*10)
if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{})
log.Fatalf("failed to bind flags: %v", err) viper.SetDefault("WATCHTOWER_NOTIFICATIONS_LEVEL", "info")
} viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT", 25)
viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG", "")
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
} }
// EnvConfig translates the command-line options into environment variables // EnvConfig translates the command-line options into environment variables
// that will initialize the api client // that will initialize the api client
func EnvConfig() error { func EnvConfig(cmd *cobra.Command) error {
var err error var err error
var host string
var tls bool var tls bool
var version string var version string
host := viper.GetString("host") flags := cmd.PersistentFlags()
tls = viper.GetBool("tlsverify")
version = viper.GetString("api-version") if host, err = flags.GetString("host"); err != nil {
return err
}
if tls, err = flags.GetBool("tlsverify"); err != nil {
return err
}
if version, err = flags.GetString("api-version"); err != nil {
return err
}
if err = setEnvOptStr("DOCKER_HOST", host); err != nil { if err = setEnvOptStr("DOCKER_HOST", host); err != nil {
return err return err
} }
@ -345,6 +344,32 @@ func EnvConfig() error {
return nil return nil
} }
// ReadFlags reads common flags used in the main program flow of watchtower
func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) {
flags := cmd.PersistentFlags()
var err error
var cleanup bool
var noRestart bool
var monitorOnly bool
var timeout time.Duration
if cleanup, err = flags.GetBool("cleanup"); err != nil {
log.Fatal(err)
}
if noRestart, err = flags.GetBool("no-restart"); err != nil {
log.Fatal(err)
}
if monitorOnly, err = flags.GetBool("monitor-only"); err != nil {
log.Fatal(err)
}
if timeout, err = flags.GetDuration("stop-timeout"); err != nil {
log.Fatal(err)
}
return cleanup, noRestart, monitorOnly, timeout
}
func setEnvOptStr(env string, opt string) error { func setEnvOptStr(env string, opt string) error {
if opt == "" || opt == os.Getenv(env) { if opt == "" || opt == os.Getenv(env) {
return nil return nil
@ -365,7 +390,9 @@ func setEnvOptBool(env string, opt bool) error {
// GetSecretsFromFiles checks if passwords/tokens/webhooks have been passed as a file instead of plaintext. // GetSecretsFromFiles checks if passwords/tokens/webhooks have been passed as a file instead of plaintext.
// If so, the value of the flag will be replaced with the contents of the file. // If so, the value of the flag will be replaced with the contents of the file.
func GetSecretsFromFiles() { func GetSecretsFromFiles(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags()
secrets := []string{ secrets := []string{
"notification-email-server-password", "notification-email-server-password",
"notification-slack-hook-url", "notification-slack-hook-url",
@ -373,19 +400,25 @@ func GetSecretsFromFiles() {
"notification-gotify-token", "notification-gotify-token",
} }
for _, secret := range secrets { for _, secret := range secrets {
getSecretFromFile(secret) getSecretFromFile(flags, secret)
} }
} }
// getSecretFromFile will check if the flag contains a reference to a file; if it does, replaces the value of the flag with the contents of the file. // getSecretFromFile will check if the flag contains a reference to a file; if it does, replaces the value of the flag with the contents of the file.
func getSecretFromFile(secret string) { func getSecretFromFile(flags *pflag.FlagSet, secret string) {
value := viper.GetString(secret) value, err := flags.GetString(secret)
if err != nil {
log.Error(err)
}
if value != "" && isFile(value) { if value != "" && isFile(value) {
file, err := ioutil.ReadFile(value) file, err := ioutil.ReadFile(value)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
viper.Set(secret, strings.TrimSpace(string(file))) err = flags.Set(secret, strings.TrimSpace(string(file)))
if err != nil {
log.Error(err)
}
} }
} }

@ -1,7 +1,6 @@
package flags package flags
import ( import (
"github.com/spf13/viper"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@ -13,11 +12,10 @@ import (
func TestEnvConfig_Defaults(t *testing.T) { func TestEnvConfig_Defaults(t *testing.T) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd) RegisterDockerFlags(cmd)
SetEnvBindings()
BindViperFlags(cmd)
err := EnvConfig() err := EnvConfig(cmd)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "unix:///var/run/docker.sock", os.Getenv("DOCKER_HOST")) assert.Equal(t, "unix:///var/run/docker.sock", os.Getenv("DOCKER_HOST"))
@ -28,14 +26,13 @@ func TestEnvConfig_Defaults(t *testing.T) {
func TestEnvConfig_Custom(t *testing.T) { func TestEnvConfig_Custom(t *testing.T) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd) RegisterDockerFlags(cmd)
SetEnvBindings()
BindViperFlags(cmd)
err := cmd.ParseFlags([]string{"--host", "some-custom-docker-host", "--tlsverify", "--api-version", "1.99"}) err := cmd.ParseFlags([]string{"--host", "some-custom-docker-host", "--tlsverify", "--api-version", "1.99"})
require.NoError(t, err) require.NoError(t, err)
err = EnvConfig() err = EnvConfig(cmd)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "some-custom-docker-host", os.Getenv("DOCKER_HOST")) assert.Equal(t, "some-custom-docker-host", os.Getenv("DOCKER_HOST"))
@ -74,11 +71,11 @@ func TestGetSecretsFromFilesWithFile(t *testing.T) {
func testGetSecretsFromFiles(t *testing.T, flagName string, expected string) { func testGetSecretsFromFiles(t *testing.T, flagName string, expected string) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
SetDefaults()
RegisterNotificationFlags(cmd) RegisterNotificationFlags(cmd)
SetEnvBindings() GetSecretsFromFiles(cmd)
BindViperFlags(cmd) value, err := cmd.PersistentFlags().GetString(flagName)
GetSecretsFromFiles() require.NoError(t, err)
value := viper.GetString(flagName)
assert.Equal(t, expected, value) assert.Equal(t, expected, value)
} }

@ -10,7 +10,6 @@ import (
"github.com/containrrr/watchtower/pkg/registry" "github.com/containrrr/watchtower/pkg/registry"
"github.com/containrrr/watchtower/pkg/registry/digest" "github.com/containrrr/watchtower/pkg/registry/digest"
"github.com/containrrr/watchtower/internal/flags"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@ -42,7 +41,7 @@ type Client interface {
// * DOCKER_HOST the docker-engine host to send api requests to // * DOCKER_HOST the docker-engine host to send api requests to
// * DOCKER_TLS_VERIFY whether to verify tls certificates // * DOCKER_TLS_VERIFY whether to verify tls certificates
// * DOCKER_API_VERSION the minimum docker api version to work with // * DOCKER_API_VERSION the minimum docker api version to work with
func NewClient(config *flags.WatchConfig) Client { func NewClient(pullImages bool, includeStopped bool, reviveStopped bool, removeVolumes bool, includeRestarting bool) Client {
cli, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv) cli, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv)
if err != nil { if err != nil {
@ -51,11 +50,11 @@ func NewClient(config *flags.WatchConfig) Client {
return dockerClient{ return dockerClient{
api: cli, api: cli,
pullImages: !config.NoPull, pullImages: pullImages,
removeVolumes: config.RemoveVolumes, removeVolumes: removeVolumes,
includeStopped: config.IncludeStopped, includeStopped: includeStopped,
reviveStopped: config.ReviveStopped, reviveStopped: reviveStopped,
includeRestarting: config.IncludeRestarting, includeRestarting: includeRestarting,
} }
} }

@ -4,7 +4,6 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"net/smtp" "net/smtp"
"os" "os"
"strings" "strings"
@ -34,17 +33,18 @@ type emailTypeNotifier struct {
delay time.Duration delay time.Duration
} }
func newEmailNotifier(_ *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
from := viper.GetString("notification-email-from") from, _ := flags.GetString("notification-email-from")
to := viper.GetString("notification-email-to") to, _ := flags.GetString("notification-email-to")
server := viper.GetString("notification-email-server") server, _ := flags.GetString("notification-email-server")
user := viper.GetString("notification-email-server-user") user, _ := flags.GetString("notification-email-server-user")
password := viper.GetString("notification-email-server-password") password, _ := flags.GetString("notification-email-server-password")
port := viper.GetInt("notification-email-server-port") port, _ := flags.GetInt("notification-email-server-port")
tlsSkipVerify := viper.GetBool("notification-email-server-tls-skip-verify") tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
delay := viper.GetInt("notification-email-delay") delay, _ := flags.GetInt("notification-email-delay")
subjecttag := viper.GetString("notification-email-subjecttag") subjecttag, _ := flags.GetString("notification-email-subjecttag")
n := &emailTypeNotifier{ n := &emailTypeNotifier{
From: from, From: from,
@ -81,13 +81,13 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
// We don't use fields in watchtower, so don't bother sending them. // We don't use fields in watchtower, so don't bother sending them.
} }
now := time.Now() t := time.Now()
header := make(map[string]string) header := make(map[string]string)
header["From"] = e.From header["From"] = e.From
header["To"] = e.To header["To"] = e.To
header["Subject"] = emailSubject header["Subject"] = emailSubject
header["Date"] = now.Format(time.RFC1123Z) header["Date"] = t.Format(time.RFC1123Z)
header["MIME-Version"] = "1.0" header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\"" header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64" header["Content-Transfer-Encoding"] = "base64"

@ -5,7 +5,6 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/spf13/viper"
"net/http" "net/http"
"strings" "strings"
@ -25,10 +24,10 @@ type gotifyTypeNotifier struct {
logLevels []log.Level logLevels []log.Level
} }
func newGotifyNotifier(_ *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := viper.Sub(".") flags := c.PersistentFlags()
gotifyURL := flags.GetString("notification-gotify-url") gotifyURL, _ := flags.GetString("notification-gotify-url")
if len(gotifyURL) < 1 { if len(gotifyURL) < 1 {
log.Fatal("Required argument --notification-gotify-url(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_URL(env) is empty.") log.Fatal("Required argument --notification-gotify-url(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_URL(env) is empty.")
} else if !(strings.HasPrefix(gotifyURL, "http://") || strings.HasPrefix(gotifyURL, "https://")) { } else if !(strings.HasPrefix(gotifyURL, "http://") || strings.HasPrefix(gotifyURL, "https://")) {
@ -37,12 +36,12 @@ func newGotifyNotifier(_ *cobra.Command, acceptedLogLevels []log.Level) t.Notifi
log.Warn("Using an HTTP url for Gotify is insecure") log.Warn("Using an HTTP url for Gotify is insecure")
} }
gotifyToken := flags.GetString("notification-gotify-token") gotifyToken, _ := flags.GetString("notification-gotify-token")
if len(gotifyToken) < 1 { if len(gotifyToken) < 1 {
log.Fatal("Required argument --notification-gotify-token(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN(env) is empty.") log.Fatal("Required argument --notification-gotify-token(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN(env) is empty.")
} }
gotifyInsecureSkipVerify := flags.GetBool("notification-gotify-tls-skip-verify") gotifyInsecureSkipVerify, _ := flags.GetBool("notification-gotify-tls-skip-verify")
n := &gotifyTypeNotifier{ n := &gotifyTypeNotifier{
gotifyURL: gotifyURL, gotifyURL: gotifyURL,

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http" "net/http"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
@ -23,14 +22,16 @@ type msTeamsTypeNotifier struct {
data bool data bool
} }
func newMsTeamsNotifier(_ *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
webHookURL := viper.GetString("notification-msteams-hook") flags := cmd.PersistentFlags()
webHookURL, _ := flags.GetString("notification-msteams-hook")
if len(webHookURL) <= 0 { if len(webHookURL) <= 0 {
log.Fatal("Required argument --notification-msteams-hook(cli) or WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL(env) is empty.") log.Fatal("Required argument --notification-msteams-hook(cli) or WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL(env) is empty.")
} }
withData := viper.GetBool("notification-msteams-data") withData, _ := flags.GetBool("notification-msteams-data")
n := &msTeamsTypeNotifier{ n := &msTeamsTypeNotifier{
levels: acceptedLogLevels, levels: acceptedLogLevels,
webHookURL: webHookURL, webHookURL: webHookURL,
@ -84,19 +85,19 @@ func (n *msTeamsTypeNotifier) Fire(entry *log.Entry) error {
jsonBody, err := json.Marshal(webHookBody) jsonBody, err := json.Marshal(webHookBody)
if err != nil { if err != nil {
fmt.Println("Failed to build JSON body for MSTeams notification: ", err) fmt.Println("Failed to build JSON body for MSTeams notificattion: ", err)
return return
} }
resp, err := http.Post(n.webHookURL, "application/json", bytes.NewBuffer(jsonBody)) resp, err := http.Post(n.webHookURL, "application/json", bytes.NewBuffer([]byte(jsonBody)))
if err != nil { if err != nil {
fmt.Println("Failed to send MSTeams notification: ", err) fmt.Println("Failed to send MSTeams notificattion: ", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
fmt.Println("Failed to send MSTeams notification. HTTP RESPONSE STATUS: ", resp.StatusCode) fmt.Println("Failed to send MSTeams notificattion. HTTP RESPONSE STATUS: ", resp.StatusCode)
if resp.Body != nil { if resp.Body != nil {
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err == nil { if err == nil {

@ -5,7 +5,6 @@ import (
"github.com/johntdyer/slackrus" "github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
// Notifier can send log output as notification to admins, with optional batching. // Notifier can send log output as notification to admins, with optional batching.
@ -17,7 +16,9 @@ type Notifier struct {
func NewNotifier(c *cobra.Command) *Notifier { func NewNotifier(c *cobra.Command) *Notifier {
n := &Notifier{} n := &Notifier{}
level := viper.GetString("notifications-level") f := c.PersistentFlags()
level, _ := f.GetString("notifications-level")
logLevel, err := log.ParseLevel(level) logLevel, err := log.ParseLevel(level)
if err != nil { if err != nil {
log.Fatalf("Notifications invalid log level: %s", err.Error()) log.Fatalf("Notifications invalid log level: %s", err.Error())
@ -26,7 +27,7 @@ func NewNotifier(c *cobra.Command) *Notifier {
acceptedLogLevels := slackrus.LevelThreshold(logLevel) acceptedLogLevels := slackrus.LevelThreshold(logLevel)
// Parse types and create notifiers. // Parse types and create notifiers.
types := viper.GetStringSlice("notifications") types, err := f.GetStringSlice("notifications")
if err != nil { if err != nil {
log.WithField("could not read notifications argument", log.Fields{"Error": err}).Fatal() log.WithField("could not read notifications argument", log.Fields{"Error": err}).Fatal()
} }

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/containrrr/shoutrrr/pkg/types" "github.com/containrrr/shoutrrr/pkg/types"
"github.com/spf13/viper"
"strings" "strings"
"text/template" "text/template"
@ -35,8 +34,9 @@ type shoutrrrTypeNotifier struct {
} }
func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
urls := viper.GetStringSlice("notification-url") urls, _ := flags.GetStringArray("notification-url")
r, err := shoutrrr.CreateSender(urls...) r, err := shoutrrr.CreateSender(urls...)
if err != nil { if err != nil {
log.Fatalf("Failed to initialize Shoutrrr notifications: %s\n", err.Error()) log.Fatalf("Failed to initialize Shoutrrr notifications: %s\n", err.Error())
@ -126,11 +126,12 @@ func (e *shoutrrrTypeNotifier) Fire(entry *log.Entry) error {
return nil return nil
} }
func getShoutrrrTemplate(_ *cobra.Command) *template.Template { func getShoutrrrTemplate(c *cobra.Command) *template.Template {
var tpl *template.Template var tpl *template.Template
var err error
tplString := viper.GetString("notification-template") flags := c.PersistentFlags()
tplString, err := flags.GetString("notification-template")
funcs := template.FuncMap{ funcs := template.FuncMap{
"ToUpper": strings.ToUpper, "ToUpper": strings.ToUpper,
@ -140,7 +141,7 @@ func getShoutrrrTemplate(_ *cobra.Command) *template.Template {
// If we succeed in getting a non-empty template configuration // If we succeed in getting a non-empty template configuration
// try to parse the template string. // try to parse the template string.
if tplString != "" { if tplString != "" && err == nil {
tpl, err = template.New("").Funcs(funcs).Parse(tplString) tpl, err = template.New("").Funcs(funcs).Parse(tplString)
} }

@ -32,7 +32,6 @@ func TestShoutrrrDefaultTemplate(t *testing.T) {
func TestShoutrrrTemplate(t *testing.T) { func TestShoutrrrTemplate(t *testing.T) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
flags.RegisterNotificationFlags(cmd) flags.RegisterNotificationFlags(cmd)
flags.BindViperFlags(cmd)
err := cmd.ParseFlags([]string{"--notification-template={{range .}}{{.Level}}: {{.Message}}{{println}}{{end}}"}) err := cmd.ParseFlags([]string{"--notification-template={{range .}}{{.Level}}: {{.Message}}{{println}}{{end}}"})
require.NoError(t, err) require.NoError(t, err)
@ -56,7 +55,6 @@ func TestShoutrrrTemplate(t *testing.T) {
func TestShoutrrrStringFunctions(t *testing.T) { func TestShoutrrrStringFunctions(t *testing.T) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
flags.RegisterNotificationFlags(cmd) flags.RegisterNotificationFlags(cmd)
flags.BindViperFlags(cmd)
err := cmd.ParseFlags([]string{"--notification-template={{range .}}{{.Level | printf \"%v\" | ToUpper }}: {{.Message | ToLower }} {{.Message | Title }}{{println}}{{end}}"}) err := cmd.ParseFlags([]string{"--notification-template={{range .}}{{.Level | printf \"%v\" | ToUpper }}: {{.Message | ToLower }} {{.Message | Title }}{{println}}{{end}}"})
require.NoError(t, err) require.NoError(t, err)
@ -79,8 +77,8 @@ func TestShoutrrrStringFunctions(t *testing.T) {
func TestShoutrrrInvalidTemplateUsesTemplate(t *testing.T) { func TestShoutrrrInvalidTemplateUsesTemplate(t *testing.T) {
cmd := new(cobra.Command) cmd := new(cobra.Command)
flags.RegisterNotificationFlags(cmd) flags.RegisterNotificationFlags(cmd)
flags.BindViperFlags(cmd)
err := cmd.ParseFlags([]string{"--notification-template={{"}) err := cmd.ParseFlags([]string{"--notification-template={{"})
require.NoError(t, err) require.NoError(t, err)
@ -110,7 +108,7 @@ type blockingRouter struct {
sent chan bool sent chan bool
} }
func (b blockingRouter) Send(_ string, _ *types.Params) []error { func (b blockingRouter) Send(message string, params *types.Params) []error {
_ = <-b.unlock _ = <-b.unlock
b.sent <- true b.sent <- true
return nil return nil

@ -5,7 +5,6 @@ import (
"github.com/johntdyer/slackrus" "github.com/johntdyer/slackrus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
const ( const (
@ -16,13 +15,14 @@ type slackTypeNotifier struct {
slackrus.SlackrusHook slackrus.SlackrusHook
} }
func newSlackNotifier(_ *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
flags := c.PersistentFlags()
hookURL := viper.GetString("notification-slack-hook-url") hookURL, _ := flags.GetString("notification-slack-hook-url")
userName := viper.GetString("notification-slack-identifier") userName, _ := flags.GetString("notification-slack-identifier")
channel := viper.GetString("notification-slack-channel") channel, _ := flags.GetString("notification-slack-channel")
emoji := viper.GetString("notification-slack-icon-emoji") emoji, _ := flags.GetString("notification-slack-icon-emoji")
iconURL := viper.GetString("notification-slack-icon-url") iconURL, _ := flags.GetString("notification-slack-icon-url")
n := &slackTypeNotifier{ n := &slackTypeNotifier{
SlackrusHook: slackrus.SlackrusHook{ SlackrusHook: slackrus.SlackrusHook{

Loading…
Cancel
Save