2017-01-24 20:07:25 +02:00
package main // import "github.com/v2tec/watchtower"
2015-07-13 23:41:04 +02:00
import (
"os"
"os/signal"
"syscall"
"time"
2016-12-30 01:23:02 +02:00
"strconv"
"github.com/robfig/cron"
2017-11-27 13:04:08 +02:00
log "github.com/sirupsen/logrus"
2016-10-13 23:57:08 +02:00
"github.com/urfave/cli"
2017-01-24 20:07:25 +02:00
"github.com/v2tec/watchtower/actions"
"github.com/v2tec/watchtower/container"
2017-10-30 08:30:44 +02:00
"github.com/v2tec/watchtower/notifications"
2015-07-13 23:41:04 +02:00
)
2017-01-24 22:41:32 +02:00
// DockerAPIMinVersion is the version of the docker API, which is minimally required by
// watchtower. Currently we require at least API 1.24 and therefore Docker 1.12 or later.
const DockerAPIMinVersion string = "1.24"
2017-02-02 19:37:12 +02:00
var version = "master"
2017-12-27 20:15:50 +02:00
var commit = "unknown"
var date = "unknown"
2017-02-02 19:37:12 +02:00
2015-07-13 23:41:04 +02:00
var (
2015-07-24 21:51:21 +02:00
client container . Client
2016-12-30 01:23:02 +02:00
scheduleSpec string
2015-07-31 20:24:27 +02:00
cleanup bool
2016-02-03 12:11:43 +02:00
noRestart bool
2017-10-30 08:30:44 +02:00
notifier * notifications . Notifier
2015-07-13 23:41:04 +02:00
)
2015-07-24 21:51:21 +02:00
func init ( ) {
log . SetLevel ( log . InfoLevel )
}
2015-07-13 23:41:04 +02:00
func main ( ) {
app := cli . NewApp ( )
app . Name = "watchtower"
2017-12-27 20:15:50 +02:00
app . Version = version + " - " + commit + " - " + date
2015-07-13 23:41:04 +02:00
app . Usage = "Automatically update running Docker containers"
2015-07-21 00:54:18 +02:00
app . Before = before
2015-07-13 23:41:04 +02:00
app . Action = start
app . Flags = [ ] cli . Flag {
2015-07-21 21:37:18 +02:00
cli . StringFlag {
Name : "host, H" ,
2015-07-24 21:51:21 +02:00
Usage : "daemon socket to connect to" ,
2015-07-21 21:37:18 +02:00
Value : "unix:///var/run/docker.sock" ,
EnvVar : "DOCKER_HOST" ,
} ,
2015-07-13 23:41:04 +02:00
cli . IntFlag {
2016-02-03 12:11:43 +02:00
Name : "interval, i" ,
Usage : "poll interval (in seconds)" ,
Value : 300 ,
EnvVar : "WATCHTOWER_POLL_INTERVAL" ,
2015-07-13 23:41:04 +02:00
} ,
2016-12-30 01:23:02 +02:00
cli . StringFlag {
Name : "schedule, s" ,
Usage : "the cron expression which defines when to update" ,
EnvVar : "WATCHTOWER_SCHEDULE" ,
} ,
2015-07-22 01:29:00 +02:00
cli . BoolFlag {
2016-02-03 12:11:43 +02:00
Name : "no-pull" ,
Usage : "do not pull new images" ,
EnvVar : "WATCHTOWER_NO_PULL" ,
2015-07-22 01:29:00 +02:00
} ,
2015-07-31 20:24:27 +02:00
cli . BoolFlag {
2016-02-03 12:11:43 +02:00
Name : "no-restart" ,
Usage : "do not restart containers" ,
2016-02-16 13:35:32 +02:00
EnvVar : "WATCHTOWER_NO_RESTART" ,
2016-02-03 12:11:43 +02:00
} ,
cli . BoolFlag {
Name : "cleanup" ,
Usage : "remove old images after updating" ,
EnvVar : "WATCHTOWER_CLEANUP" ,
2015-07-31 20:24:27 +02:00
} ,
2015-07-24 21:51:21 +02:00
cli . BoolFlag {
Name : "tlsverify" ,
Usage : "use TLS and verify the remote" ,
EnvVar : "DOCKER_TLS_VERIFY" ,
} ,
2017-10-17 06:53:30 +02:00
cli . BoolFlag {
Name : "label-enable" ,
Usage : "watch containers where the com.centurylinklabs.watchtower.enable label is true" ,
EnvVar : "WATCHTOWER_LABEL_ENABLE" ,
} ,
2015-07-22 23:58:16 +02:00
cli . BoolFlag {
Name : "debug" ,
Usage : "enable debug mode with verbose logging" ,
} ,
2017-10-30 08:30:44 +02:00
cli . StringSliceFlag {
2018-01-22 21:36:32 +02:00
Name : "notifications" ,
Value : & cli . StringSlice { } ,
2017-11-27 13:04:08 +02:00
Usage : "notification types to send (valid: email, slack)" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATIONS" ,
} ,
cli . StringFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-from" ,
Usage : "Address to send notification e-mails from" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_FROM" ,
} ,
cli . StringFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-to" ,
Usage : "Address to send notification e-mails to" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_TO" ,
} ,
cli . StringFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-server" ,
Usage : "SMTP server to send notification e-mails through" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_SERVER" ,
} ,
2017-12-27 21:25:49 +02:00
cli . IntFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-server-port" ,
Usage : "SMTP server port to send notification e-mails through" ,
2017-12-27 21:25:49 +02:00
Value : 25 ,
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT" ,
} ,
2018-01-22 21:36:32 +02:00
cli . BoolFlag {
Name : "notification-email-server-tls-skip-verify" ,
Usage : "Controls whether watchtower verifies the SMTP server's certificate chain and host name. " +
"If set, TLS accepts any certificate " +
"presented by the server and any host name in that certificate. " +
"In this mode, TLS is susceptible to man-in-the-middle attacks. " +
"This should be used only for testing." ,
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY" ,
} ,
2017-10-30 08:30:44 +02:00
cli . StringFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-server-user" ,
Usage : "SMTP server user for sending notifications" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER" ,
} ,
cli . StringFlag {
2018-01-22 21:36:32 +02:00
Name : "notification-email-server-password" ,
Usage : "SMTP server password for sending notifications" ,
2017-10-30 08:30:44 +02:00
EnvVar : "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD" ,
} ,
2017-11-27 13:04:08 +02:00
cli . StringFlag {
Name : "notification-slack-hook-url" ,
Usage : "The Slack Hook URL to send notifications to" ,
EnvVar : "WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL" ,
} ,
cli . StringFlag {
Name : "notification-slack-identifier" ,
Usage : "A string which will be used to identify the messages coming from this watchtower instance. Default if omitted is \"watchtower\"" ,
EnvVar : "WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER" ,
Value : "watchtower" ,
} ,
cli . StringFlag {
Name : "notification-slack-level" ,
Usage : "The log level used for sending notifications through Slack. Default if omitted is \"info\". Possible values: \"panic\",\"fatal\",\"error\",\"warn\",\"info\" or \"debug\"" ,
EnvVar : "WATCHTOWER_NOTIFICATION_SLACK_LEVEL" ,
Value : "info" ,
} ,
2015-07-13 23:41:04 +02:00
}
2015-07-24 21:51:21 +02:00
if err := app . Run ( os . Args ) ; err != nil {
log . Fatal ( err )
}
}
func before ( c * cli . Context ) error {
if c . GlobalBool ( "debug" ) {
log . SetLevel ( log . DebugLevel )
}
2016-12-30 01:23:02 +02:00
pollingSet := c . IsSet ( "interval" )
cronSet := c . IsSet ( "schedule" )
if pollingSet && cronSet {
log . Fatal ( "Only schedule or interval can be defined, not both." )
} else if cronSet {
scheduleSpec = c . String ( "schedule" )
} else {
scheduleSpec = "@every " + strconv . Itoa ( c . Int ( "interval" ) ) + "s"
}
2015-07-31 20:24:27 +02:00
cleanup = c . GlobalBool ( "cleanup" )
2016-02-03 12:11:43 +02:00
noRestart = c . GlobalBool ( "no-restart" )
2015-07-24 21:51:21 +02:00
2016-10-14 12:10:48 +02:00
// configure environment vars for client
err := envConfig ( c )
2015-07-24 21:51:21 +02:00
if err != nil {
return err
}
2017-10-17 06:53:30 +02:00
client = container . NewClient ( ! c . GlobalBool ( "no-pull" ) , c . GlobalBool ( "label-enable" ) )
2017-10-30 08:30:44 +02:00
notifier = notifications . NewNotifier ( c )
2015-07-24 21:51:21 +02:00
return nil
}
2016-12-30 01:23:02 +02:00
func start ( c * cli . Context ) error {
2015-08-04 18:32:01 +02:00
names := c . Args ( )
2015-07-31 20:24:27 +02:00
if err := actions . CheckPrereqs ( client , cleanup ) ; err != nil {
2015-07-24 21:51:21 +02:00
log . Fatal ( err )
}
2016-12-30 01:23:02 +02:00
tryLockSem := make ( chan bool , 1 )
tryLockSem <- true
cron := cron . New ( )
err := cron . AddFunc (
scheduleSpec ,
func ( ) {
select {
2018-01-22 21:36:32 +02:00
case v := <- tryLockSem :
2016-12-30 01:23:02 +02:00
defer func ( ) { tryLockSem <- v } ( )
2017-10-30 08:30:44 +02:00
notifier . StartNotification ( )
2016-12-30 01:23:02 +02:00
if err := actions . Update ( client , names , cleanup , noRestart ) ; err != nil {
2017-10-30 08:30:44 +02:00
log . Println ( err )
2016-12-30 01:23:02 +02:00
}
2017-10-30 08:30:44 +02:00
notifier . SendNotification ( )
2016-12-30 01:23:02 +02:00
default :
log . Debug ( "Skipped another update already running." )
}
nextRuns := cron . Entries ( )
if len ( nextRuns ) > 0 {
log . Debug ( "Scheduled next run: " + nextRuns [ 0 ] . Next . String ( ) )
}
} )
2015-07-24 21:51:21 +02:00
2016-12-30 01:23:02 +02:00
if err != nil {
return err
2015-07-24 21:51:21 +02:00
}
2015-07-13 23:41:04 +02:00
2016-12-30 01:23:02 +02:00
log . Info ( "First run: " + cron . Entries ( ) [ 0 ] . Schedule . Next ( time . Now ( ) ) . String ( ) )
cron . Start ( )
2015-07-13 23:41:04 +02:00
// Graceful shut-down on SIGINT/SIGTERM
2016-12-30 01:23:02 +02:00
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
os . Exit ( 1 )
return nil
2015-07-13 23:41:04 +02:00
}
2016-10-14 12:10:48 +02:00
func setEnvOptStr ( env string , opt string ) error {
2016-10-18 14:54:41 +02:00
if opt != "" && opt != os . Getenv ( env ) {
2016-10-14 12:10:48 +02:00
err := os . Setenv ( env , opt )
2016-10-18 14:54:41 +02:00
if err != nil {
2016-10-14 12:10:48 +02:00
return err
2015-07-24 21:51:21 +02:00
}
2016-10-14 12:10:48 +02:00
}
return nil
}
2015-07-21 00:54:18 +02:00
2016-10-14 12:10:48 +02:00
func setEnvOptBool ( env string , opt bool ) error {
2016-10-18 14:54:41 +02:00
if opt == true {
2016-10-14 12:10:48 +02:00
return setEnvOptStr ( env , "1" )
}
return nil
}
2015-07-24 21:51:21 +02:00
2016-10-14 12:10:48 +02:00
// envConfig translates the command-line options into environment variables
// that will initialize the api client
func envConfig ( c * cli . Context ) error {
var err error
2015-07-13 23:41:04 +02:00
2016-10-14 12:10:48 +02:00
err = setEnvOptStr ( "DOCKER_HOST" , c . GlobalString ( "host" ) )
err = setEnvOptBool ( "DOCKER_TLS_VERIFY" , c . GlobalBool ( "tlsverify" ) )
2017-01-24 22:41:32 +02:00
err = setEnvOptStr ( "DOCKER_API_VERSION" , DockerAPIMinVersion )
2015-07-21 21:37:18 +02:00
2016-10-14 12:10:48 +02:00
return err
2015-07-21 21:37:18 +02:00
}