@ -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 )
}
}
}
}
}