fix config marshalling and interval/schedule

pull/1253/head
nils måsén 5 years ago committed by nils måsén
parent a5c60a9fe6
commit c305ee385c

@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"github.com/containrrr/watchtower/internal/meta"
"math" "math"
"net/http" "net/http"
"os" "os"
@ -11,36 +10,28 @@ import (
"syscall" "syscall"
"time" "time"
apiMetrics "github.com/containrrr/watchtower/pkg/api/metrics"
"github.com/containrrr/watchtower/pkg/api/update"
"github.com/containrrr/watchtower/internal/actions" "github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/internal/flags" "github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/internal/meta"
"github.com/containrrr/watchtower/pkg/api" "github.com/containrrr/watchtower/pkg/api"
apiMetrics "github.com/containrrr/watchtower/pkg/api/metrics"
"github.com/containrrr/watchtower/pkg/api/update"
"github.com/containrrr/watchtower/pkg/container" "github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/filters" "github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/metrics" "github.com/containrrr/watchtower/pkg/metrics"
"github.com/containrrr/watchtower/pkg/notifications" "github.com/containrrr/watchtower/pkg/notifications"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron" "github.com/robfig/cron"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var ( var (
client container.Client client container.Client
scheduleSpec string notifier *notifications.Notifier
cleanup bool c flags.WatchConfig
noRestart bool
monitorOnly bool
enableLabel bool
notifier t.Notifier
timeout time.Duration
lifecycleHooks bool
rollingRestart bool
scope string
// Set on build using ldflags
) )
var rootCmd = NewRootCommand() var rootCmd = NewRootCommand()
@ -60,10 +51,11 @@ func NewRootCommand() *cobra.Command {
} }
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
@ -75,9 +67,9 @@ 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, _ []string) {
f := cmd.PersistentFlags()
if enabled, _ := f.GetBool("no-color"); enabled { // First apply all the settings that affect the output
if viper.GetBool("no-color") {
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
DisableColors: true, DisableColors: true,
}) })
@ -88,97 +80,72 @@ func PreRun(cmd *cobra.Command, _ []string) {
}) })
} }
if enabled, _ := f.GetBool("debug"); enabled { if viper.GetBool("debug") {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }
if enabled, _ := f.GetBool("trace"); enabled { if viper.GetBool("trace") {
log.SetLevel(log.TraceLevel) log.SetLevel(log.TraceLevel)
} }
pollingSet := f.Changed("interval") interval := viper.GetInt("interval")
schedule, _ := f.GetString("schedule")
cronLen := len(schedule)
if pollingSet && cronLen > 0 { // If empty, set schedule using interval helper value
log.Fatal("Only schedule or interval can be defined, not both.") if viper.GetString("schedule") == "" {
} else if cronLen > 0 { viper.Set("schedule", fmt.Sprintf("@every %ds", interval))
scheduleSpec, _ = f.GetString("schedule") } else if interval != flags.DefaultInterval {
} else { log.Fatal("only schedule or interval can be defined, not both")
interval, _ := f.GetInt("interval")
scheduleSpec = "@every " + strconv.Itoa(interval) + "s"
} }
flags.GetSecretsFromFiles(cmd) // Then load the rest of the settings
cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd) err := viper.Unmarshal(&c)
if err != nil {
log.Fatalf("unable to decode into struct, %v", err)
}
flags.GetSecretsFromFiles()
if timeout < 0 { if c.Timeout <= 0 {
log.Fatal("Please specify a positive value for timeout value.") log.Fatal("Please specify a positive value for timeout value.")
} }
enableLabel, _ = f.GetBool("label-enable") log.Debugf("Using scope %v", c.Scope)
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
log.Debug(scope) if err = flags.EnvConfig(); err != nil {
log.Fatalf("failed to setup environment variables: %v", err)
// configure environment vars for client
err := flags.EnvConfig(cmd)
if err != nil {
log.Fatal(err)
} }
noPull, _ := f.GetBool("no-pull") if c.MonitorOnly && c.NoPull {
includeStopped, _ := f.GetBool("include-stopped")
includeRestarting, _ := f.GetBool("include-restarting")
reviveStopped, _ := f.GetBool("revive-stopped")
removeVolumes, _ := f.GetBool("remove-volumes")
warnOnHeadPullFailed, _ := f.GetString("warn-on-head-failure")
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( client = container.NewClient(&c)
!noPull,
includeStopped,
reviveStopped,
removeVolumes,
includeRestarting,
warnOnHeadPullFailed,
)
notifier = notifications.NewNotifier(cmd) notifier = notifications.NewNotifier()
} }
// Run is the main execution flow of the command // Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) { func Run(_ *cobra.Command, names []string) {
filter, filterDesc := filters.BuildFilter(names, enableLabel, scope) filter, filterDesc := filters.BuildFilter(names, c.EnableLabel, c.Scope)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update") if c.RollingRestart && c.MonitorOnly {
enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics")
unblockHTTPAPI, _ := c.PersistentFlags().GetBool("http-api-periodic-polls")
apiToken, _ := c.PersistentFlags().GetString("http-api-token")
if rollingRestart && monitorOnly {
log.Fatal("Rolling restarts is not compatible with the global monitor only flag") log.Fatal("Rolling restarts is not compatible with the global monitor only flag")
} }
awaitDockerClient() awaitDockerClient()
if err := actions.CheckForSanity(client, filter, rollingRestart); err != nil { if err := actions.CheckForSanity(client, filter, c.RollingRestart); err != nil {
logNotifyExit(err) logNotifyExit(err)
} }
if runOnce { if c.RunOnce {
writeStartupMessage(c, time.Time{}, filterDesc) writeStartupMessage(time.Time{}, filterDesc)
runUpdatesWithNotifications(filter) runUpdatesWithNotifications(filter)
notifier.Close() notifier.Close()
os.Exit(0) os.Exit(0)
return return
} }
if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup, scope); err != nil { if err := actions.CheckForMultipleWatchtowerInstances(client, c.Cleanup, c.Scope); err != nil {
logNotifyExit(err) logNotifyExit(err)
} }
@ -186,28 +153,28 @@ func Run(c *cobra.Command, names []string) {
updateLock := make(chan bool, 1) updateLock := make(chan bool, 1)
updateLock <- true updateLock <- true
httpAPI := api.New(apiToken) httpAPI := api.New(c.HTTPAPIToken)
if enableUpdateAPI { if c.EnableUpdateAPI {
updateHandler := update.New(func() { runUpdatesWithNotifications(filter) }, updateLock) updateHandler := update.New(func() { runUpdatesWithNotifications(filter) }, updateLock)
httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle) httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle)
// If polling isn't enabled the scheduler is never started and // If polling isn't enabled the scheduler is never started and
// we need to trigger the startup messages manually. // we need to trigger the startup messages manually.
if !unblockHTTPAPI { if !c.UpdateAPIWithScheduler {
writeStartupMessage(c, time.Time{}, filterDesc) writeStartupMessage(c, time.Time{}, filterDesc)
} }
} }
if enableMetricsAPI { if c.EnableMetricsAPI {
metricsHandler := apiMetrics.New() metricsHandler := apiMetrics.New()
httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle) httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
} }
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && err != http.ErrServerClosed { if err := httpAPI.Start(c.EnableUpdateAPI && !c.UpdateAPIWithScheduler); err != nil {
log.Error("failed to start API", err) log.Error("failed to start API", err)
} }
if err := runUpgradesOnSchedule(c, filter, filterDesc, updateLock); err != nil { if err := runUpgradesOnSchedule(filter, filterDesc, updateLock); err != nil {
log.Error(err) log.Error(err)
} }
@ -265,11 +232,8 @@ func formatDuration(d time.Duration) string {
} }
func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) { func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
var startupLog *log.Entry var startupLog *log.Entry
if noStartupMessage { if c.NoStartupMessage {
startupLog = notifications.LocalLog startupLog = notifications.LocalLog
} else { } else {
startupLog = log.NewEntry(log.StandardLogger()) startupLog = log.NewEntry(log.StandardLogger())
@ -298,12 +262,12 @@ func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
startupLog.Info("Periodic runs are not enabled.") startupLog.Info("Periodic runs are not enabled.")
} }
if enableUpdateAPI { if c.EnableUpdateAPI {
// TODO: make listen port configurable // TODO: make listen port configurable
startupLog.Info("The HTTP API is enabled at :8080.") startupLog.Info("The HTTP API is enabled at :8080.")
} }
if !noStartupMessage { if !c.NoStartupMessage {
// Send the queued up startup messages, not including the trace warning below (to make sure it's noticed) // Send the queued up startup messages, not including the trace warning below (to make sure it's noticed)
notifier.SendNotification(nil) notifier.SendNotification(nil)
} }
@ -313,7 +277,7 @@ func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
} }
} }
func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string, lock chan bool) error { func runUpgradesOnSchedule(filter t.Filter, filtering string, lock chan bool) error {
if lock == nil { if lock == nil {
lock = make(chan bool, 1) lock = make(chan bool, 1)
lock <- true lock <- true
@ -321,7 +285,7 @@ func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string,
scheduler := cron.New() scheduler := cron.New()
err := scheduler.AddFunc( err := scheduler.AddFunc(
scheduleSpec, c.Schedule,
func() { func() {
select { select {
case v := <-lock: case v := <-lock:
@ -344,7 +308,7 @@ func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string,
return err return err
} }
writeStartupMessage(c, scheduler.Entries()[0].Schedule.Next(time.Now()), filtering) writeStartupMessage(scheduler.Entries()[0].Schedule.Next(time.Now()), filtering)
scheduler.Start() scheduler.Start()
@ -364,12 +328,12 @@ func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
notifier.StartNotification() notifier.StartNotification()
updateParams := t.UpdateParams{ updateParams := t.UpdateParams{
Filter: filter, Filter: filter,
Cleanup: cleanup, Cleanup: c.Cleanup,
NoRestart: noRestart, NoRestart: c.NoRestart,
Timeout: timeout, Timeout: c.Timeout,
MonitorOnly: monitorOnly, MonitorOnly: c.MonitorOnly,
LifecycleHooks: lifecycleHooks, LifecycleHooks: c.LifecycleHooks,
RollingRestart: rollingRestart, RollingRestart: c.RollingRestart,
} }
result, err := actions.Update(client, updateParams) result, err := actions.Update(client, updateParams)
if err != nil { if err != nil {

@ -0,0 +1,34 @@
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"`
HTTPAPIToken string `mapstructure:"http-api-token"`
Timeout Duration `mapstructure:"stop-timeout"`
Scope string
EnableUpdateAPI bool `mapstructure:"http-api-update"`
EnableMetricsAPI bool `mapstructure:"http-api-metrics"`
UpdateAPIWithScheduler bool `mapstructure:"http-api-periodic-polls"`
WarnOnHeadFailed string `mapstructure:"warn-on-head-failure"`
NoColor bool `mapstructure:"no-color"`
}

@ -15,13 +15,14 @@ import (
// DockerAPIMinVersion is the minimum version of the docker api required to // DockerAPIMinVersion is the minimum version of the docker api required to
// use watchtower // use watchtower
const DockerAPIMinVersion string = "1.25" const DockerAPIMinVersion string = "1.25"
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", viper.GetString("DOCKER_HOST"), "daemon socket to connect to") flags.StringP("host", "H", "unix:///var/run/docker.sock", "daemon socket to connect to")
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote") flags.BoolP("tlsverify", "v", false, "use TLS and verify the remote")
flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client") flags.StringP("api-version", "a", DockerAPIMinVersion, "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
@ -30,126 +31,118 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
flags.IntP( flags.IntP(
"interval", "interval",
"i", "i",
viper.GetInt("WATCHTOWER_POLL_INTERVAL"), DefaultInterval, // viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
"Poll interval (in seconds)") "poll interval (in seconds)")
flags.StringP( flags.String(
"schedule", "schedule",
"s", "s",
viper.GetString("WATCHTOWER_SCHEDULE"), "",
"The cron expression which defines when to update") "The cron expression which defines when to update")
//viper.GetString("WATCHTOWER_SCHEDULE"),
flags.DurationP( flags.DurationP(
"stop-timeout", "stop-timeout",
"t", "t",
viper.GetDuration("WATCHTOWER_TIMEOUT"), 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",
"", "",
viper.GetBool("WATCHTOWER_NO_PULL"), false, // viper.GetBool("WATCHTOWER_NO_PULL"),
"Do not pull any new images") "Do not pull any new images")
flags.BoolP( flags.Bool(
"no-restart", "no-restart",
"", false, // viper.GetBool("WATCHTOWER_NO_RESTART"),
viper.GetBool("WATCHTOWER_NO_RESTART"),
"Do not restart any containers") "Do not restart any containers")
flags.BoolP( flags.Bool(
"no-startup-message", "no-startup-message",
"", false, // viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
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",
viper.GetBool("WATCHTOWER_CLEANUP"), 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",
"", "",
viper.GetBool("WATCHTOWER_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",
viper.GetBool("WATCHTOWER_LABEL_ENABLE"), 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",
viper.GetBool("WATCHTOWER_DEBUG"), false, // viper.GetBool("WATCHTOWER_DEBUG"),
"Enable debug mode with verbose logging") "Enable debug mode with verbose logging")
flags.BoolP( flags.Bool(
"trace", "trace",
"", false, // viper.GetBool("WATCHTOWER_TRACE"),
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",
viper.GetBool("WATCHTOWER_MONITOR_ONLY"), 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",
viper.GetBool("WATCHTOWER_RUN_ONCE"), false, // viper.GetBool("WATCHTOWER_RUN_ONCE"),
"Run once now and exit") "Run once now and exit")
flags.BoolP( flags.BoolP(
"include-restarting", "include-restarting",
"", "",
viper.GetBool("WATCHTOWER_INCLUDE_RESTARTING"), false, // viper.GetBool("WATCHTOWER_INCLUDE_RESTARTING"),
"Will also include restarting containers") "Will also include restarting containers")
flags.BoolP( flags.BoolP(
"include-stopped", "include-stopped",
"S", "S",
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"), false, // viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
"Will also include created and exited containers") "Will also include created and exited containers")
flags.BoolP( flags.Bool(
"revive-stopped", "revive-stopped",
"", false, // viper.GetBool("WATCHTOWER_REVIVE_STOPPED"),
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.Bool(
"enable-lifecycle-hooks", "enable-lifecycle-hooks",
"", false, // viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
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.Bool(
"rolling-restart", "rolling-restart",
"", false, // viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
"Restart containers one at a time") "Restart containers one at a time")
flags.BoolP( flags.Bool(
"http-api-update", "http-api-update",
"", false, // viper.GetBool("WATCHTOWER_HTTP_API_UPDATE"),
viper.GetBool("WATCHTOWER_HTTP_API_UPDATE"),
"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.BoolP( flags.Bool(
"http-api-metrics", "http-api-metrics",
"", false, // viper.GetBool("WATCHTOWER_HTTP_API_METRICS"),
viper.GetBool("WATCHTOWER_HTTP_API_METRICS"),
"Runs Watchtower with the Prometheus metrics API enabled") "Runs Watchtower with the Prometheus metrics API enabled")
flags.StringP( flags.String(
"http-api-token", "http-api-token",
"", "", // viper.GetString("WATCHTOWER_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.")
flags.BoolP( flags.BoolP(
"http-api-periodic-polls", "http-api-periodic-polls",
@ -160,12 +153,11 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
flags.BoolP( flags.BoolP(
"no-color", "no-color",
"", "",
viper.IsSet("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.String(
"scope", "scope",
"", "", // viper.GetString("WATCHTOWER_SCOPE"),
viper.GetString("WATCHTOWER_SCOPE"),
"Defines a monitoring scope for the Watchtower instance.") "Defines a monitoring scope for the Watchtower instance.")
} }
@ -176,159 +168,162 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
flags.StringSliceP( flags.StringSliceP(
"notifications", "notifications",
"n", "n",
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"), []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.String( flags.String(
"notifications-level", "notifications-level",
viper.GetString("WATCHTOWER_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.IntP( flags.Int(
"notifications-delay", "notifications-delay",
"", 0, // viper.GetInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
viper.GetInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
"Delay before sending notifications, expressed in seconds") "Delay before sending notifications, expressed in seconds")
flags.StringP( flags.String(
"notifications-hostname", "notifications-hostname",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"), // viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
"Custom hostname for notification titles") "Custom hostname for notification titles")
flags.StringP( flags.String(
"notification-email-from", "notification-email-from",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"), // viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
"Address to send notification emails from") "Address to send notification emails from")
flags.StringP( flags.String(
"notification-email-to", "notification-email-to",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"), // viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
"Address to send notification emails to") "Address to send notification emails to")
flags.IntP( flags.Int(
"notification-email-delay", "notification-email-delay",
"", 0,
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"), //viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
"Delay before sending notifications, expressed in seconds") "Delay before sending notifications, expressed in seconds")
flags.StringP( flags.String(
"notification-email-server", "notification-email-server",
"", "",
viper.GetString("WATCHTOWER_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.Int(
"notification-email-server-port", "notification-email-server-port",
"", 25,
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"), // 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.Bool(
"notification-email-server-tls-skip-verify", "notification-email-server-tls-skip-verify",
"", false,
viper.GetBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"), // 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.String(
"notification-email-server-user", "notification-email-server-user",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-email-server-password", "notification-email-server-password",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-email-subjecttag", "notification-email-subjecttag",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-slack-hook-url", "notification-slack-hook-url",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-slack-identifier", "notification-slack-identifier",
"", "watchtower",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"), // 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.String(
"notification-slack-channel", "notification-slack-channel",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-slack-icon-emoji", "notification-slack-icon-emoji",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-slack-icon-url", "notification-slack-icon-url",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-msteams-hook", "notification-msteams-hook",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"), // 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.Bool(
"notification-msteams-data", "notification-msteams-data",
"", false,
viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"), // 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.String(
"notification-gotify-url", "notification-gotify-url",
"", "",
viper.GetString("WATCHTOWER_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.String(
"notification-gotify-token", "notification-gotify-token",
"", "",
viper.GetString("WATCHTOWER_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.Bool(
"notification-gotify-tls-skip-verify", "notification-gotify-tls-skip-verify",
"", false,
viper.GetBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"), // 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.String( flags.String(
"notification-template", "notification-template",
viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"), "",
// viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
"The shoutrrr text/template for the messages") "The shoutrrr text/template for the messages")
flags.StringArray( flags.StringArray(
"notification-url", "notification-url",
viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"), []string{},
// viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
"The shoutrrr URL to send notifications to") "The shoutrrr URL to send notifications to")
flags.Bool("notification-report", flags.Bool("notification-report",
viper.GetBool("WATCHTOWER_NOTIFICATION_REPORT"), false,
// viper.GetBool("WATCHTOWER_NOTIFICATION_REPORT"),
"Use the session report as the notification template data") "Use the session report as the notification template data")
flags.String( flags.String(
"warn-on-head-failure", "warn-on-head-failure",
viper.GetString("WATCHTOWER_WARN_ON_HEAD_FAILURE"), "auto",
// viper.GetString("WATCHTOWER_WARN_ON_HEAD_FAILURE"),
"When to warn about HEAD pull requests failing. Possible values: always, auto or never") "When to warn about HEAD pull requests failing. Possible values: always, auto or never")
} }
@ -337,36 +332,23 @@ Should only be used for testing.`)
func SetDefaults() { func SetDefaults() {
day := (time.Hour * 24).Seconds() day := (time.Hour * 24).Seconds()
viper.AutomaticEnv() viper.AutomaticEnv()
viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock") }
viper.SetDefault("DOCKER_API_VERSION", DockerAPIMinVersion)
viper.SetDefault("WATCHTOWER_POLL_INTERVAL", day) // BindViperFlags binds the cmd PFlags to the viper configuration
viper.SetDefault("WATCHTOWER_TIMEOUT", time.Second*10) func BindViperFlags(cmd *cobra.Command) {
viper.SetDefault("WATCHTOWER_NOTIFICATIONS", []string{}) if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil {
viper.SetDefault("WATCHTOWER_NOTIFICATIONS_LEVEL", "info") log.Fatalf("failed to bind flags: %v", err)
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(cmd *cobra.Command) error { func EnvConfig() error {
var err error var err error
var host string
var tls bool
var version string
flags := cmd.PersistentFlags()
if host, err = flags.GetString("host"); err != nil { host := viper.GetString("host")
return err tls = viper.GetBool("tlsverify")
} version = viper.GetString("api-version")
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
} }
@ -380,29 +362,14 @@ func EnvConfig(cmd *cobra.Command) error {
} }
// ReadFlags reads common flags used in the main program flow of watchtower // ReadFlags reads common flags used in the main program flow of watchtower
func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) { func ReadFlags() (cleanup bool, noRestart bool, monitorOnly bool, timeout 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 { cleanup = viper.GetBool("cleanup")
log.Fatal(err) noRestart = viper.GetBool("no-restart")
} monitorOnly = viper.GetBool("monitor-only")
if noRestart, err = flags.GetBool("no-restart"); err != nil { timeout = viper.GetDuration("stop-timeout")
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 return
} }
func setEnvOptStr(env string, opt string) error { func setEnvOptStr(env string, opt string) error {
@ -425,9 +392,7 @@ 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(rootCmd *cobra.Command) { func GetSecretsFromFiles() {
flags := rootCmd.PersistentFlags()
secrets := []string{ secrets := []string{
"notification-email-server-password", "notification-email-server-password",
"notification-slack-hook-url", "notification-slack-hook-url",
@ -435,25 +400,19 @@ func GetSecretsFromFiles(rootCmd *cobra.Command) {
"notification-gotify-token", "notification-gotify-token",
} }
for _, secret := range secrets { for _, secret := range secrets {
getSecretFromFile(flags, secret) getSecretFromFile(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(flags *pflag.FlagSet, secret string) { func getSecretFromFile(secret string) {
value, err := flags.GetString(secret) value := viper.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)
} }
err = flags.Set(secret, strings.TrimSpace(string(file))) viper.Set(secret, strings.TrimSpace(string(file)))
if err != nil {
log.Error(err)
}
} }
} }

Loading…
Cancel
Save