You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
5.0 KiB
Go
209 lines
5.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/spf13/viper"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containrrr/watchtower/internal/actions"
|
|
"github.com/containrrr/watchtower/internal/flags"
|
|
"github.com/containrrr/watchtower/pkg/api"
|
|
"github.com/containrrr/watchtower/pkg/container"
|
|
"github.com/containrrr/watchtower/pkg/filters"
|
|
"github.com/containrrr/watchtower/pkg/notifications"
|
|
t "github.com/containrrr/watchtower/pkg/types"
|
|
"github.com/robfig/cron"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
client container.Client
|
|
notifier *notifications.Notifier
|
|
c flags.WatchConfig
|
|
)
|
|
|
|
var rootCmd = &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,
|
|
}
|
|
|
|
func init() {
|
|
flags.RegisterDockerFlags(rootCmd)
|
|
flags.RegisterSystemFlags(rootCmd)
|
|
flags.RegisterNotificationFlags(rootCmd)
|
|
flags.SetEnvBindings()
|
|
flags.BindViperFlags(rootCmd)
|
|
}
|
|
|
|
// Execute the root func and exit in case of errors
|
|
func Execute() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// PreRun is a lifecycle hook that runs before the command is executed.
|
|
func PreRun(cmd *cobra.Command, _ []string) {
|
|
|
|
// First apply all the settings that affect the output
|
|
if viper.GetBool("no-color") {
|
|
log.SetFormatter(&log.TextFormatter{
|
|
DisableColors: true,
|
|
})
|
|
} else {
|
|
// enable logrus built-in support for https://bixense.com/clicolors/
|
|
log.SetFormatter(&log.TextFormatter{
|
|
EnvironmentOverrideColors: true,
|
|
})
|
|
}
|
|
|
|
if viper.GetBool("debug") {
|
|
log.SetLevel(log.DebugLevel)
|
|
}
|
|
if viper.GetBool("trace") {
|
|
log.SetLevel(log.TraceLevel)
|
|
}
|
|
|
|
interval := viper.GetInt("interval")
|
|
|
|
// If empty, set schedule using interval helper value
|
|
if viper.GetString("schedule") == "" {
|
|
viper.Set("schedule", fmt.Sprintf("@every %ds", interval))
|
|
} else if interval != flags.DefaultInterval {
|
|
log.Fatal("only schedule or interval can be defined, not both")
|
|
}
|
|
|
|
// Then load the rest of the settings
|
|
err := viper.Unmarshal(&c)
|
|
if err != nil {
|
|
log.Fatalf("unable to decode into struct, %v", err)
|
|
}
|
|
|
|
flags.GetSecretsFromFiles()
|
|
|
|
if c.Timeout <= 0 {
|
|
log.Fatal("Please specify a positive value for timeout value.")
|
|
}
|
|
|
|
log.Debugf("Using scope %v", c.Scope)
|
|
|
|
if err = flags.EnvConfig(); err != nil {
|
|
log.Fatalf("failed to setup environment variables: %v", err)
|
|
}
|
|
|
|
if c.MonitorOnly && c.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.")
|
|
}
|
|
|
|
client = container.NewClient(&c)
|
|
|
|
notifier = notifications.NewNotifier(cmd)
|
|
}
|
|
|
|
// Run is the main execution flow of the command
|
|
func Run(_ *cobra.Command, names []string) {
|
|
filter := filters.BuildFilter(names, c.EnableLabel, c.Scope)
|
|
|
|
if c.RunOnce {
|
|
if !c.NoStartupMessage {
|
|
log.Info("Running a one time update.")
|
|
}
|
|
runUpdatesWithNotifications(filter)
|
|
notifier.Close()
|
|
os.Exit(0)
|
|
return
|
|
}
|
|
|
|
if err := actions.CheckForMultipleWatchtowerInstances(client, c.Cleanup, c.Scope); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if c.HTTPAPI {
|
|
if err := api.SetupHTTPUpdates(c.HTTPAPIToken, func() { runUpdatesWithNotifications(filter) }); err != nil {
|
|
log.Fatal(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
api.WaitForHTTPUpdates()
|
|
}
|
|
|
|
if err := runUpgradesOnSchedule(filter); err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
os.Exit(1)
|
|
}
|
|
|
|
func runUpgradesOnSchedule(filter t.Filter) error {
|
|
tryLockSem := make(chan bool, 1)
|
|
tryLockSem <- true
|
|
|
|
runner := cron.New()
|
|
err := runner.AddFunc(
|
|
viper.GetString("schedule"),
|
|
func() {
|
|
select {
|
|
case v := <-tryLockSem:
|
|
defer func() { tryLockSem <- v }()
|
|
runUpdatesWithNotifications(filter)
|
|
default:
|
|
log.Debug("Skipped another update already running.")
|
|
}
|
|
|
|
nextRuns := runner.Entries()
|
|
if len(nextRuns) > 0 {
|
|
log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
|
|
}
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !viper.GetBool("no-startup-message") {
|
|
log.Info("Starting Watchtower and scheduling first run: " + runner.Entries()[0].Schedule.Next(time.Now()).String())
|
|
}
|
|
|
|
runner.Start()
|
|
|
|
// Graceful shut-down on SIGINT/SIGTERM
|
|
interrupt := make(chan os.Signal, 1)
|
|
signal.Notify(interrupt, os.Interrupt)
|
|
signal.Notify(interrupt, syscall.SIGTERM)
|
|
|
|
<-interrupt
|
|
runner.Stop()
|
|
log.Info("Waiting for running update to be finished...")
|
|
<-tryLockSem
|
|
return nil
|
|
}
|
|
|
|
func runUpdatesWithNotifications(filter t.Filter) {
|
|
notifier.StartNotification()
|
|
updateParams := t.UpdateParams{
|
|
Filter: filter,
|
|
Cleanup: c.Cleanup,
|
|
NoRestart: c.NoRestart,
|
|
Timeout: c.Timeout,
|
|
MonitorOnly: c.MonitorOnly,
|
|
LifecycleHooks: c.LifecycleHooks,
|
|
RollingRestart: c.RollingRestart,
|
|
}
|
|
err := actions.Update(client, updateParams)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
notifier.SendNotification()
|
|
}
|