2019-06-22 22:04:36 +02:00
package cmd
import (
2021-06-27 00:19:52 +02:00
"fmt"
2022-03-13 12:11:32 +01:00
"github.com/containrrr/watchtower/internal/config"
2021-03-28 21:04:11 +02:00
"math"
2019-07-21 18:00:56 +02:00
"os"
"os/signal"
2020-12-21 23:08:23 +01:00
"strconv"
2021-03-28 21:04:11 +02:00
"strings"
2019-07-21 18:00:56 +02:00
"syscall"
"time"
2019-07-21 22:22:30 +02:00
"github.com/containrrr/watchtower/internal/actions"
2020-12-12 16:34:04 +01:00
"github.com/containrrr/watchtower/internal/meta"
2020-04-20 11:17:14 -03:00
"github.com/containrrr/watchtower/pkg/api"
2020-12-12 16:34:04 +01:00
apiMetrics "github.com/containrrr/watchtower/pkg/api/metrics"
"github.com/containrrr/watchtower/pkg/api/update"
2019-07-21 20:15:04 +02:00
"github.com/containrrr/watchtower/pkg/container"
2020-03-13 10:38:33 +01:00
"github.com/containrrr/watchtower/pkg/filters"
2021-03-28 21:04:11 +02:00
"github.com/containrrr/watchtower/pkg/metrics"
2019-07-21 22:22:30 +02:00
"github.com/containrrr/watchtower/pkg/notifications"
2019-07-21 19:58:19 +02:00
t "github.com/containrrr/watchtower/pkg/types"
2020-12-12 16:34:04 +01:00
2019-06-22 22:04:36 +02:00
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2020-12-12 16:34:04 +01:00
"github.com/spf13/viper"
2019-06-22 22:04:36 +02:00
)
var (
2020-12-12 16:34:04 +01:00
client container . Client
2021-06-27 00:19:52 +02:00
notifier t . Notifier
2022-03-13 12:11:32 +01:00
c config . WatchConfig
2019-06-22 22:04:36 +02:00
)
2021-01-06 20:06:56 +01:00
var rootCmd = NewRootCommand ( )
// NewRootCommand creates the root command for watchtower
func NewRootCommand ( ) * cobra . Command {
return & cobra . Command {
Use : "watchtower" ,
Short : "Automatically updates running Docker containers" ,
Long : `
Watchtower automatically updates running Docker containers whenever a new image is released .
More information available at https : //github.com/containrrr/watchtower/.
` ,
Run : Run ,
PreRun : PreRun ,
}
2019-06-22 22:04:36 +02:00
}
func init ( ) {
2022-03-13 12:11:32 +01:00
config . RegisterDockerOptions ( rootCmd )
config . RegisterSystemOptions ( rootCmd )
config . RegisterNotificationOptions ( rootCmd )
config . BindViperFlags ( rootCmd )
2019-06-22 22:04:36 +02:00
}
2019-06-23 00:32:50 +02:00
// Execute the root func and exit in case of errors
2019-06-22 22:04:36 +02:00
func Execute ( ) {
if err := rootCmd . Execute ( ) ; err != nil {
2019-06-23 00:32:50 +02:00
log . Fatal ( err )
2019-06-22 22:04:36 +02:00
}
}
2019-06-23 00:32:50 +02:00
// PreRun is a lifecycle hook that runs before the command is executed.
2021-06-27 00:19:52 +02:00
func PreRun ( _ * cobra . Command , _ [ ] string ) {
2019-06-22 22:04:36 +02:00
2020-12-12 16:34:04 +01:00
// First apply all the settings that affect the output
2022-03-13 12:11:32 +01:00
if config . GetBool ( config . NoColor ) {
2020-08-21 22:54:16 +02:00
log . SetFormatter ( & log . TextFormatter {
DisableColors : true ,
} )
} else {
// enable logrus built-in support for https://bixense.com/clicolors/
log . SetFormatter ( & log . TextFormatter {
EnvironmentOverrideColors : true ,
} )
}
2022-03-13 12:11:32 +01:00
if config . GetBool ( config . Debug ) {
2019-06-22 22:04:36 +02:00
log . SetLevel ( log . DebugLevel )
}
2022-03-13 12:11:32 +01:00
if config . GetBool ( config . Trace ) {
2020-05-11 06:09:52 +02:00
log . SetLevel ( log . TraceLevel )
}
2019-06-22 22:04:36 +02:00
2022-03-13 12:11:32 +01:00
interval := config . GetInt ( config . Interval )
2019-06-22 22:04:36 +02:00
2020-12-12 16:34:04 +01:00
// If empty, set schedule using interval helper value
2022-03-13 12:11:32 +01:00
if config . GetString ( config . Schedule ) == "" {
viper . Set ( string ( config . Schedule ) , fmt . Sprintf ( "@every %ds" , interval ) )
} else if interval != config . DefaultInterval {
2020-12-12 16:34:04 +01:00
log . Fatal ( "only schedule or interval can be defined, not both" )
2020-12-21 15:17:45 +01:00
}
2019-06-22 22:04:36 +02:00
2020-12-12 16:34:04 +01:00
// Then load the rest of the settings
err := viper . Unmarshal ( & c )
if err != nil {
log . Fatalf ( "unable to decode into struct, %v" , err )
}
2022-03-13 12:11:32 +01:00
config . GetSecretsFromFiles ( )
2020-12-21 15:17:45 +01:00
2020-12-12 16:34:04 +01:00
if c . Timeout <= 0 {
2019-06-22 22:04:36 +02:00
log . Fatal ( "Please specify a positive value for timeout value." )
}
2019-07-27 01:37:16 +02:00
2020-12-12 16:34:04 +01:00
log . Debugf ( "Using scope %v" , c . Scope )
2020-12-21 23:08:23 +01:00
2022-03-13 12:11:32 +01:00
if err = config . EnvConfig ( ) ; err != nil {
2020-12-12 16:34:04 +01:00
log . Fatalf ( "failed to setup environment variables: %v" , err )
2019-06-22 22:04:36 +02:00
}
2020-12-12 16:34:04 +01:00
if c . MonitorOnly && c . NoPull {
2020-08-08 20:43:01 +04:00
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." )
}
2020-12-12 16:34:04 +01:00
client = container . NewClient ( & c )
2019-06-22 22:04:36 +02:00
2020-12-12 16:34:04 +01:00
notifier = notifications . NewNotifier ( )
2019-06-22 22:04:36 +02:00
}
2019-06-23 00:32:50 +02:00
// Run is the main execution flow of the command
2020-12-12 16:34:04 +01:00
func Run ( _ * cobra . Command , names [ ] string ) {
filter , filterDesc := filters . BuildFilter ( names , c . EnableLabel , c . Scope )
if c . RollingRestart && c . MonitorOnly {
2021-04-18 18:37:35 +02:00
log . Fatal ( "Rolling restarts is not compatible with the global monitor only flag" )
}
awaitDockerClient ( )
2020-12-12 16:34:04 +01:00
if err := actions . CheckForSanity ( client , filter , c . RollingRestart ) ; err != nil {
2021-04-18 18:37:35 +02:00
logNotifyExit ( err )
}
2020-12-12 16:34:04 +01:00
if c . RunOnce {
writeStartupMessage ( time . Time { } , filterDesc )
2019-06-22 22:04:36 +02:00
runUpdatesWithNotifications ( filter )
2020-08-08 22:55:51 +02:00
notifier . Close ( )
2019-08-25 13:02:08 +02:00
os . Exit ( 0 )
2019-06-22 22:04:36 +02:00
return
}
2020-12-12 16:34:04 +01:00
if err := actions . CheckForMultipleWatchtowerInstances ( client , c . Cleanup , c . Scope ) ; err != nil {
2021-04-18 18:37:35 +02:00
logNotifyExit ( err )
2019-06-22 22:04:36 +02:00
}
2021-04-27 22:18:45 +02:00
// The lock is shared between the scheduler and the HTTP API. It only allows one update to run at a time.
updateLock := make ( chan bool , 1 )
updateLock <- true
2020-12-12 16:34:04 +01:00
httpAPI := api . New ( c . HTTPAPIToken )
2020-12-21 23:08:23 +01:00
2020-12-12 16:34:04 +01:00
if c . EnableUpdateAPI {
2021-04-27 22:18:45 +02:00
updateHandler := update . New ( func ( ) { runUpdatesWithNotifications ( filter ) } , updateLock )
2021-01-06 22:28:32 +01:00
httpAPI . RegisterFunc ( updateHandler . Path , updateHandler . Handle )
2021-06-27 00:19:52 +02:00
// If polling isn't enabled the scheduler is never started, and
2021-11-29 16:07:26 +02:00
// we need to trigger the startup messages manually.
2020-12-12 16:34:04 +01:00
if ! c . UpdateAPIWithScheduler {
2021-06-27 00:19:52 +02:00
writeStartupMessage ( time . Time { } , filterDesc )
2021-11-29 16:07:26 +02:00
}
2021-01-06 22:28:32 +01:00
}
2020-08-21 15:13:47 -03:00
2020-12-12 16:34:04 +01:00
if c . EnableMetricsAPI {
2021-03-28 21:04:11 +02:00
metricsHandler := apiMetrics . New ( )
2021-01-06 22:28:32 +01:00
httpAPI . RegisterHandler ( metricsHandler . Path , metricsHandler . Handle )
2020-08-21 15:13:47 -03:00
}
2020-12-12 16:34:04 +01:00
if err := httpAPI . Start ( c . EnableUpdateAPI && ! c . UpdateAPIWithScheduler ) ; err != nil {
2021-03-28 21:04:11 +02:00
log . Error ( "failed to start API" , err )
}
2021-01-06 22:28:32 +01:00
2020-12-12 16:34:04 +01:00
if err := runUpgradesOnSchedule ( filter , filterDesc , updateLock ) ; err != nil {
2019-07-22 12:10:57 +02:00
log . Error ( err )
}
2019-06-22 22:04:36 +02:00
os . Exit ( 1 )
}
2021-04-18 18:37:35 +02:00
func logNotifyExit ( err error ) {
log . Error ( err )
notifier . Close ( )
os . Exit ( 1 )
}
func awaitDockerClient ( ) {
log . Debug ( "Sleeping for a second to ensure the docker api client has been properly initialized." )
time . Sleep ( 1 * time . Second )
}
2021-03-28 21:04:11 +02:00
func formatDuration ( d time . Duration ) string {
sb := strings . Builder { }
hours := int64 ( d . Hours ( ) )
minutes := int64 ( math . Mod ( d . Minutes ( ) , 60 ) )
seconds := int64 ( math . Mod ( d . Seconds ( ) , 60 ) )
if hours == 1 {
sb . WriteString ( "1 hour" )
} else if hours != 0 {
sb . WriteString ( strconv . FormatInt ( hours , 10 ) )
sb . WriteString ( " hours" )
}
if hours != 0 && ( seconds != 0 || minutes != 0 ) {
sb . WriteString ( ", " )
}
if minutes == 1 {
sb . WriteString ( "1 minute" )
} else if minutes != 0 {
sb . WriteString ( strconv . FormatInt ( minutes , 10 ) )
sb . WriteString ( " minutes" )
}
if minutes != 0 && ( seconds != 0 ) {
sb . WriteString ( ", " )
}
if seconds == 1 {
sb . WriteString ( "1 second" )
} else if seconds != 0 || ( hours == 0 && minutes == 0 ) {
sb . WriteString ( strconv . FormatInt ( seconds , 10 ) )
sb . WriteString ( " seconds" )
}
return sb . String ( )
}
2021-06-27 00:19:52 +02:00
func writeStartupMessage ( sched time . Time , filtering string ) {
2021-09-19 18:06:14 +02:00
var startupLog * log . Entry
2020-12-12 16:34:04 +01:00
if c . NoStartupMessage {
2021-09-19 18:06:14 +02:00
startupLog = notifications . LocalLog
} else {
startupLog = log . NewEntry ( log . StandardLogger ( ) )
// Batch up startup messages to send them as a single notification
notifier . StartNotification ( )
}
startupLog . Info ( "Watchtower " , meta . Version )
notifierNames := notifier . GetNames ( )
if len ( notifierNames ) > 0 {
startupLog . Info ( "Using notifications: " + strings . Join ( notifierNames , ", " ) )
} else {
startupLog . Info ( "Using no notifications" )
}
startupLog . Info ( filtering )
if ! sched . IsZero ( ) {
until := formatDuration ( time . Until ( sched ) )
startupLog . Info ( "Scheduling first run: " + sched . Format ( "2006-01-02 15:04:05 -0700 MST" ) )
startupLog . Info ( "Note that the first check will be performed in " + until )
2021-06-27 00:19:52 +02:00
} else if c . RunOnce {
startupLog . Info ( "Running a one time update." )
2021-09-19 18:06:14 +02:00
} else {
2021-11-29 16:07:26 +02:00
startupLog . Info ( "Periodic runs are not enabled." )
}
2020-12-12 16:34:04 +01:00
if c . EnableUpdateAPI {
2021-11-29 16:07:26 +02:00
// TODO: make listen port configurable
startupLog . Info ( "The HTTP API is enabled at :8080." )
2021-09-19 18:06:14 +02:00
}
2020-12-12 16:34:04 +01:00
if ! c . NoStartupMessage {
2021-09-19 18:06:14 +02:00
// Send the queued up startup messages, not including the trace warning below (to make sure it's noticed)
notifier . SendNotification ( nil )
}
if log . IsLevelEnabled ( log . TraceLevel ) {
startupLog . Warn ( "Trace level enabled: log will include sensitive information as credentials and tokens" )
2021-03-28 21:04:11 +02:00
}
}
2020-12-12 16:34:04 +01:00
func runUpgradesOnSchedule ( filter t . Filter , filtering string , lock chan bool ) error {
2021-04-27 22:18:45 +02:00
if lock == nil {
lock = make ( chan bool , 1 )
lock <- true
}
2019-06-22 22:04:36 +02:00
2021-03-28 21:04:11 +02:00
scheduler := cron . New ( )
err := scheduler . AddFunc (
2020-12-12 16:34:04 +01:00
c . Schedule ,
2019-06-22 22:04:36 +02:00
func ( ) {
select {
2021-04-27 22:18:45 +02:00
case v := <- lock :
defer func ( ) { lock <- v } ( )
2021-01-06 22:28:32 +01:00
metric := runUpdatesWithNotifications ( filter )
2021-03-28 21:04:11 +02:00
metrics . RegisterScan ( metric )
2019-06-22 22:04:36 +02:00
default :
2021-01-06 22:28:32 +01:00
// Update was skipped
2021-03-28 21:04:11 +02:00
metrics . RegisterScan ( nil )
2019-06-22 22:04:36 +02:00
log . Debug ( "Skipped another update already running." )
}
2021-03-28 21:04:11 +02:00
nextRuns := scheduler . Entries ( )
2019-06-22 22:04:36 +02:00
if len ( nextRuns ) > 0 {
log . Debug ( "Scheduled next run: " + nextRuns [ 0 ] . Next . String ( ) )
}
} )
if err != nil {
return err
}
2020-12-12 16:34:04 +01:00
writeStartupMessage ( scheduler . Entries ( ) [ 0 ] . Schedule . Next ( time . Now ( ) ) , filtering )
2020-03-13 10:38:33 +01:00
2021-03-28 21:04:11 +02:00
scheduler . Start ( )
2019-06-22 22:04:36 +02:00
// Graceful shut-down on SIGINT/SIGTERM
interrupt := make ( chan os . Signal , 1 )
signal . Notify ( interrupt , os . Interrupt )
signal . Notify ( interrupt , syscall . SIGTERM )
<- interrupt
2021-03-28 21:04:11 +02:00
scheduler . Stop ( )
2019-06-22 22:04:36 +02:00
log . Info ( "Waiting for running update to be finished..." )
2021-04-27 22:18:45 +02:00
<- lock
2019-06-22 22:04:36 +02:00
return nil
}
2021-03-28 21:04:11 +02:00
func runUpdatesWithNotifications ( filter t . Filter ) * metrics . Metric {
2019-06-22 22:04:36 +02:00
notifier . StartNotification ( )
2020-01-11 23:35:25 +01:00
updateParams := t . UpdateParams {
2019-07-27 01:37:16 +02:00
Filter : filter ,
2020-12-12 16:34:04 +01:00
Cleanup : c . Cleanup ,
NoRestart : c . NoRestart ,
Timeout : c . Timeout ,
MonitorOnly : c . MonitorOnly ,
LifecycleHooks : c . LifecycleHooks ,
RollingRestart : c . RollingRestart ,
2019-06-22 22:04:36 +02:00
}
2021-06-27 09:05:01 +02:00
result , err := actions . Update ( client , updateParams )
2019-06-22 22:04:36 +02:00
if err != nil {
2021-04-24 18:29:05 +02:00
log . Error ( err )
2019-06-22 22:04:36 +02:00
}
2021-06-27 09:05:01 +02:00
notifier . SendNotification ( result )
metricResults := metrics . NewMetric ( result )
2021-11-18 14:08:38 +01:00
notifications . LocalLog . WithFields ( log . Fields {
"Scanned" : metricResults . Scanned ,
"Updated" : metricResults . Updated ,
"Failed" : metricResults . Failed ,
} ) . Info ( "Session done" )
2021-03-28 21:04:11 +02:00
return metricResults
2019-06-22 22:04:36 +02:00
}