mirror of https://github.com/containrrr/watchtower
				
				
				
			
			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.
		
		
		
		
		
			
		
			
				
	
	
		
			257 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			257 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	metrics2 "github.com/containrrr/watchtower/pkg/metrics"
 | 
						|
	"os"
 | 
						|
	"os/signal"
 | 
						|
	"strconv"
 | 
						|
	"syscall"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/containrrr/watchtower/pkg/api/metrics"
 | 
						|
	"github.com/containrrr/watchtower/pkg/api/update"
 | 
						|
 | 
						|
	"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
 | 
						|
	scheduleSpec   string
 | 
						|
	cleanup        bool
 | 
						|
	noRestart      bool
 | 
						|
	monitorOnly    bool
 | 
						|
	enableLabel    bool
 | 
						|
	notifier       *notifications.Notifier
 | 
						|
	timeout        time.Duration
 | 
						|
	lifecycleHooks bool
 | 
						|
	rollingRestart bool
 | 
						|
	scope          string
 | 
						|
)
 | 
						|
 | 
						|
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,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func init() {
 | 
						|
	flags.SetDefaults()
 | 
						|
	flags.RegisterDockerFlags(rootCmd)
 | 
						|
	flags.RegisterSystemFlags(rootCmd)
 | 
						|
	flags.RegisterNotificationFlags(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, args []string) {
 | 
						|
	f := cmd.PersistentFlags()
 | 
						|
 | 
						|
	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,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	if enabled, _ := f.GetBool("debug"); enabled {
 | 
						|
		log.SetLevel(log.DebugLevel)
 | 
						|
	}
 | 
						|
	if enabled, _ := f.GetBool("trace"); enabled {
 | 
						|
		log.SetLevel(log.TraceLevel)
 | 
						|
	}
 | 
						|
 | 
						|
	pollingSet := f.Changed("interval")
 | 
						|
	schedule, _ := f.GetString("schedule")
 | 
						|
	cronLen := len(schedule)
 | 
						|
 | 
						|
	if pollingSet && cronLen > 0 {
 | 
						|
		log.Fatal("Only schedule or interval can be defined, not both.")
 | 
						|
	} else if cronLen > 0 {
 | 
						|
		scheduleSpec, _ = f.GetString("schedule")
 | 
						|
	} else {
 | 
						|
		interval, _ := f.GetInt("interval")
 | 
						|
		scheduleSpec = "@every " + strconv.Itoa(interval) + "s"
 | 
						|
	}
 | 
						|
 | 
						|
	flags.GetSecretsFromFiles(cmd)
 | 
						|
	cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd)
 | 
						|
 | 
						|
	if timeout < 0 {
 | 
						|
		log.Fatal("Please specify a positive value for timeout value.")
 | 
						|
	}
 | 
						|
 | 
						|
	enableLabel, _ = f.GetBool("label-enable")
 | 
						|
	lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
 | 
						|
	rollingRestart, _ = f.GetBool("rolling-restart")
 | 
						|
	scope, _ = f.GetString("scope")
 | 
						|
 | 
						|
	log.Debug(scope)
 | 
						|
 | 
						|
	// configure environment vars for client
 | 
						|
	err := flags.EnvConfig(cmd)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	noPull, _ := f.GetBool("no-pull")
 | 
						|
	includeStopped, _ := f.GetBool("include-stopped")
 | 
						|
	includeRestarting, _ := f.GetBool("include-restarting")
 | 
						|
	reviveStopped, _ := f.GetBool("revive-stopped")
 | 
						|
	removeVolumes, _ := f.GetBool("remove-volumes")
 | 
						|
 | 
						|
	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.")
 | 
						|
	}
 | 
						|
 | 
						|
	client = container.NewClient(
 | 
						|
		!noPull,
 | 
						|
		includeStopped,
 | 
						|
		reviveStopped,
 | 
						|
		removeVolumes,
 | 
						|
		includeRestarting,
 | 
						|
	)
 | 
						|
 | 
						|
	notifier = notifications.NewNotifier(cmd)
 | 
						|
}
 | 
						|
 | 
						|
// Run is the main execution flow of the command
 | 
						|
func Run(c *cobra.Command, names []string) {
 | 
						|
	filter := filters.BuildFilter(names, enableLabel, scope)
 | 
						|
	runOnce, _ := c.PersistentFlags().GetBool("run-once")
 | 
						|
	enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
 | 
						|
	enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics")
 | 
						|
 | 
						|
	apiToken, _ := c.PersistentFlags().GetString("http-api-token")
 | 
						|
 | 
						|
	if runOnce {
 | 
						|
		if noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message"); !noStartupMessage {
 | 
						|
			log.Info("Running a one time update.")
 | 
						|
		}
 | 
						|
		runUpdatesWithNotifications(filter)
 | 
						|
		notifier.Close()
 | 
						|
		os.Exit(0)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup, scope); err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	httpAPI := api.New(apiToken)
 | 
						|
 | 
						|
	if enableUpdateAPI {
 | 
						|
		updateHandler := update.New(func() { runUpdatesWithNotifications(filter) })
 | 
						|
		httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle)
 | 
						|
	}
 | 
						|
 | 
						|
	if enableMetricsAPI {
 | 
						|
		metricsHandler := metrics.New()
 | 
						|
		httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
 | 
						|
	}
 | 
						|
 | 
						|
	httpAPI.Start(enableUpdateAPI)
 | 
						|
 | 
						|
	if err := runUpgradesOnSchedule(c, filter); err != nil {
 | 
						|
		log.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter) error {
 | 
						|
	tryLockSem := make(chan bool, 1)
 | 
						|
	tryLockSem <- true
 | 
						|
 | 
						|
	cron := cron.New()
 | 
						|
	err := cron.AddFunc(
 | 
						|
		scheduleSpec,
 | 
						|
		func() {
 | 
						|
			select {
 | 
						|
			case v := <-tryLockSem:
 | 
						|
				defer func() { tryLockSem <- v }()
 | 
						|
				metric := runUpdatesWithNotifications(filter)
 | 
						|
				metrics2.RegisterScan(metric)
 | 
						|
			default:
 | 
						|
				// Update was skipped
 | 
						|
				metrics2.RegisterScan(nil)
 | 
						|
				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
 | 
						|
	}
 | 
						|
 | 
						|
	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())
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
func runUpdatesWithNotifications(filter t.Filter) *metrics2.Metric {
 | 
						|
 | 
						|
	notifier.StartNotification()
 | 
						|
	updateParams := t.UpdateParams{
 | 
						|
		Filter:         filter,
 | 
						|
		Cleanup:        cleanup,
 | 
						|
		NoRestart:      noRestart,
 | 
						|
		Timeout:        timeout,
 | 
						|
		MonitorOnly:    monitorOnly,
 | 
						|
		LifecycleHooks: lifecycleHooks,
 | 
						|
		RollingRestart: rollingRestart,
 | 
						|
	}
 | 
						|
	metrics, err := actions.Update(client, updateParams)
 | 
						|
	if err != nil {
 | 
						|
		log.Println(err)
 | 
						|
	}
 | 
						|
	notifier.SendNotification()
 | 
						|
	return metrics
 | 
						|
}
 |