2019-06-22 22:04:36 +02:00
package cmd
import (
2019-07-21 18:00:56 +02:00
"os"
"os/signal"
"strconv"
"syscall"
"time"
2019-07-21 22:22:30 +02:00
"github.com/containrrr/watchtower/internal/actions"
2019-06-22 22:04:36 +02:00
"github.com/containrrr/watchtower/internal/flags"
2020-04-20 16:17:14 +02:00
"github.com/containrrr/watchtower/pkg/api"
2019-07-21 20:15:04 +02:00
"github.com/containrrr/watchtower/pkg/container"
2020-03-13 11:38:33 +02:00
"github.com/containrrr/watchtower/pkg/filters"
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"
2019-06-22 22:04:36 +02:00
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
2019-07-27 01:37:16 +02:00
client container . Client
scheduleSpec string
cleanup bool
noRestart bool
monitorOnly bool
enableLabel bool
notifier * notifications . Notifier
timeout time . Duration
lifecycleHooks bool
2020-08-21 22:35:46 +02:00
rollingRestart bool
2020-10-03 22:25:17 +02:00
scope string
2019-06-22 22:04:36 +02:00
)
var rootCmd = & cobra . Command {
2019-07-21 18:00:56 +02:00
Use : "watchtower" ,
Short : "Automatically updates running Docker containers" ,
Long : `
2019-06-22 22:04:36 +02:00
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 ,
}
func init ( ) {
flags . SetDefaults ( )
flags . RegisterDockerFlags ( rootCmd )
flags . RegisterSystemFlags ( rootCmd )
flags . RegisterNotificationFlags ( rootCmd )
}
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.
2019-06-22 22:04:36 +02:00
func PreRun ( cmd * cobra . Command , args [ ] string ) {
f := cmd . PersistentFlags ( )
2020-08-21 22:54:16 +02:00
if enabled , _ := f . GetBool ( "no-color" ) ; enabled {
log . SetFormatter ( & log . TextFormatter {
DisableColors : true ,
} )
} else {
// enable logrus built-in support for https://bixense.com/clicolors/
log . SetFormatter ( & log . TextFormatter {
EnvironmentOverrideColors : true ,
} )
}
2019-07-22 12:10:57 +02:00
if enabled , _ := f . GetBool ( "debug" ) ; enabled {
2019-06-22 22:04:36 +02:00
log . SetLevel ( log . DebugLevel )
}
2020-05-11 06:09:52 +02:00
if enabled , _ := f . GetBool ( "trace" ) ; enabled {
log . SetLevel ( log . TraceLevel )
}
2019-06-22 22:04:36 +02:00
pollingSet := f . Changed ( "interval" )
schedule , _ := f . GetString ( "schedule" )
cronLen := len ( schedule )
2019-07-02 18:24:19 +02:00
if pollingSet && cronLen > 0 {
2019-06-22 22:04:36 +02:00
log . Fatal ( "Only schedule or interval can be defined, not both." )
2019-07-02 18:24:19 +02:00
} else if cronLen > 0 {
2019-06-22 22:04:36 +02:00
scheduleSpec , _ = f . GetString ( "schedule" )
} else {
interval , _ := f . GetInt ( "interval" )
scheduleSpec = "@every " + strconv . Itoa ( interval ) + "s"
}
2020-06-10 12:14:47 +02:00
flags . GetSecretsFromFiles ( cmd )
2019-06-22 22:04:36 +02:00
cleanup , noRestart , monitorOnly , timeout = flags . ReadFlags ( cmd )
if timeout < 0 {
log . Fatal ( "Please specify a positive value for timeout value." )
}
2019-07-27 01:37:16 +02:00
2019-06-22 22:04:36 +02:00
enableLabel , _ = f . GetBool ( "label-enable" )
2019-07-27 01:37:16 +02:00
lifecycleHooks , _ = f . GetBool ( "enable-lifecycle-hooks" )
2020-08-21 22:35:46 +02:00
rollingRestart , _ = f . GetBool ( "rolling-restart" )
2020-08-21 20:13:47 +02:00
scope , _ = f . GetString ( "scope" )
log . Debug ( scope )
2019-06-22 22:04:36 +02:00
// configure environment vars for client
2019-08-25 12:37:20 +02:00
err := flags . EnvConfig ( cmd )
2019-06-22 22:04:36 +02:00
if err != nil {
log . Fatal ( err )
}
noPull , _ := f . GetBool ( "no-pull" )
includeStopped , _ := f . GetBool ( "include-stopped" )
2019-11-13 12:16:37 +02:00
reviveStopped , _ := f . GetBool ( "revive-stopped" )
2019-07-21 18:00:56 +02:00
removeVolumes , _ := f . GetBool ( "remove-volumes" )
2019-07-27 01:37:16 +02:00
2020-08-08 18:43:01 +02:00
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." )
}
2019-06-22 22:04:36 +02:00
client = container . NewClient (
! noPull ,
includeStopped ,
2019-11-13 12:16:37 +02:00
reviveStopped ,
2019-07-21 18:00:56 +02:00
removeVolumes ,
2019-06-22 22:04:36 +02:00
)
notifier = notifications . NewNotifier ( cmd )
}
2019-06-23 00:32:50 +02:00
// Run is the main execution flow of the command
2019-06-22 22:04:36 +02:00
func Run ( c * cobra . Command , names [ ] string ) {
2020-08-21 20:13:47 +02:00
filter := filters . BuildFilter ( names , enableLabel , scope )
2019-06-22 22:04:36 +02:00
runOnce , _ := c . PersistentFlags ( ) . GetBool ( "run-once" )
2020-04-20 16:17:14 +02:00
httpAPI , _ := c . PersistentFlags ( ) . GetBool ( "http-api" )
2019-06-22 22:04:36 +02:00
if runOnce {
2020-03-26 17:58:57 +02:00
if noStartupMessage , _ := c . PersistentFlags ( ) . GetBool ( "no-startup-message" ) ; ! noStartupMessage {
log . Info ( "Running a one time update." )
}
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-08-21 20:13:47 +02:00
if err := actions . CheckForMultipleWatchtowerInstances ( client , cleanup , scope ) ; err != nil {
2019-06-22 22:04:36 +02:00
log . Fatal ( err )
}
2020-08-21 20:13:47 +02:00
if httpAPI {
apiToken , _ := c . PersistentFlags ( ) . GetString ( "http-api-token" )
if err := api . SetupHTTPUpdates ( apiToken , func ( ) { runUpdatesWithNotifications ( filter ) } ) ; err != nil {
log . Fatal ( err )
os . Exit ( 1 )
}
api . WaitForHTTPUpdates ( )
}
2020-03-13 11:38:33 +02:00
if err := runUpgradesOnSchedule ( c , filter ) ; err != nil {
2019-07-22 12:10:57 +02:00
log . Error ( err )
}
2019-06-22 22:04:36 +02:00
os . Exit ( 1 )
}
2020-03-13 11:38:33 +02:00
func runUpgradesOnSchedule ( c * cobra . Command , filter t . Filter ) error {
2019-06-22 22:04:36 +02:00
tryLockSem := make ( chan bool , 1 )
tryLockSem <- true
cron := cron . New ( )
err := cron . AddFunc (
scheduleSpec ,
func ( ) {
select {
case v := <- tryLockSem :
defer func ( ) { tryLockSem <- v } ( )
runUpdatesWithNotifications ( filter )
default :
log . Debug ( "Skipped another update already running." )
}
nextRuns := cron . Entries ( )
if len ( nextRuns ) > 0 {
log . Debug ( "Scheduled next run: " + nextRuns [ 0 ] . Next . String ( ) )
}
} )
if err != nil {
return err
}
2020-03-13 11:38:33 +02:00
if noStartupMessage , _ := c . PersistentFlags ( ) . GetBool ( "no-startup-message" ) ; ! noStartupMessage {
log . Info ( "Starting Watchtower and scheduling first run: " + cron . Entries ( ) [ 0 ] . Schedule . Next ( time . Now ( ) ) . String ( ) )
}
2019-06-22 22:04:36 +02:00
cron . Start ( )
// Graceful shut-down on SIGINT/SIGTERM
interrupt := make ( chan os . Signal , 1 )
signal . Notify ( interrupt , os . Interrupt )
signal . Notify ( interrupt , syscall . SIGTERM )
<- interrupt
cron . Stop ( )
log . Info ( "Waiting for running update to be finished..." )
<- tryLockSem
return nil
}
2019-07-21 19:58:19 +02:00
func runUpdatesWithNotifications ( filter t . Filter ) {
2019-06-22 22:04:36 +02:00
notifier . StartNotification ( )
2020-01-12 00:35:25 +02:00
updateParams := t . UpdateParams {
2019-07-27 01:37:16 +02:00
Filter : filter ,
Cleanup : cleanup ,
NoRestart : noRestart ,
Timeout : timeout ,
MonitorOnly : monitorOnly ,
LifecycleHooks : lifecycleHooks ,
2020-08-21 22:35:46 +02:00
RollingRestart : rollingRestart ,
2019-06-22 22:04:36 +02:00
}
err := actions . Update ( client , updateParams )
if err != nil {
log . Println ( err )
}
notifier . SendNotification ( )
}