Allow running periodic updates with enabled HTTP API (#916)

* Allow running periodic updates with enabled HTTP API

* Add --http-api-periodic-polls to docs
pull/944/head
DasSkelett 4 years ago committed by GitHub
parent e308521a95
commit 6b155a111a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -156,6 +156,7 @@ func Run(c *cobra.Command, names []string) {
runOnce, _ := c.PersistentFlags().GetBool("run-once") runOnce, _ := c.PersistentFlags().GetBool("run-once")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update") enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics") enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics")
unblockHTTPAPI, _ := c.PersistentFlags().GetBool("http-api-periodic-polls")
apiToken, _ := c.PersistentFlags().GetString("http-api-token") apiToken, _ := c.PersistentFlags().GetString("http-api-token")
if rollingRestart && monitorOnly { if rollingRestart && monitorOnly {
@ -180,10 +181,14 @@ func Run(c *cobra.Command, names []string) {
logNotifyExit(err) logNotifyExit(err)
} }
// 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
httpAPI := api.New(apiToken) httpAPI := api.New(apiToken)
if enableUpdateAPI { if enableUpdateAPI {
updateHandler := update.New(func() { runUpdatesWithNotifications(filter) }) updateHandler := update.New(func() { runUpdatesWithNotifications(filter) }, updateLock)
httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle) httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle)
} }
@ -192,11 +197,11 @@ func Run(c *cobra.Command, names []string) {
httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle) httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
} }
if err := httpAPI.Start(enableUpdateAPI); err != nil { if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil {
log.Error("failed to start API", err) log.Error("failed to start API", err)
} }
if err := runUpgradesOnSchedule(c, filter, filterDesc); err != nil { if err := runUpgradesOnSchedule(c, filter, filterDesc, updateLock); err != nil {
log.Error(err) log.Error(err)
} }
@ -275,17 +280,19 @@ func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
} }
} }
func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string) error { func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string, lock chan bool) error {
tryLockSem := make(chan bool, 1) if lock == nil {
tryLockSem <- true lock = make(chan bool, 1)
lock <- true
}
scheduler := cron.New() scheduler := cron.New()
err := scheduler.AddFunc( err := scheduler.AddFunc(
scheduleSpec, scheduleSpec,
func() { func() {
select { select {
case v := <-tryLockSem: case v := <-lock:
defer func() { tryLockSem <- v }() defer func() { lock <- v }()
metric := runUpdatesWithNotifications(filter) metric := runUpdatesWithNotifications(filter)
metrics.RegisterScan(metric) metrics.RegisterScan(metric)
default: default:
@ -316,7 +323,7 @@ func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string)
<-interrupt <-interrupt
scheduler.Stop() scheduler.Stop()
log.Info("Waiting for running update to be finished...") log.Info("Waiting for running update to be finished...")
<-tryLockSem <-lock
return nil return nil
} }

@ -255,6 +255,16 @@ Environment Variable: WATCHTOWER_HTTP_API_TOKEN
Default: - Default: -
``` ```
## HTTP API periodic polls
Keep running periodic updates if the HTTP API mode is enabled, otherwise the HTTP API would prevent periodic polls.
```
Argument: --http-api-periodic-polls
Environment Variable: WATCHTOWER_HTTP_API_PERIODIC_POLLS
Type: Boolean
Default: false
```
## Filter by scope ## Filter by scope
Update containers that have a `com.centurylinklabs.watchtower.scope` label set with the same value as the given argument. Update containers that have a `com.centurylinklabs.watchtower.scope` label set with the same value as the given argument.
This enables [running multiple instances](https://containrrr.github.io/watchtower/running-multiple-instances). This enables [running multiple instances](https://containrrr.github.io/watchtower/running-multiple-instances).

@ -28,6 +28,8 @@ services:
- 8080:8080 - 8080:8080
``` ```
By default, enabling this mode prevents periodic polls (i.e. what is specified using `--interval` or `--schedule`). To run periodic updates regardless, pass `--http-api-periodic-polls`.
Notice that there is an environment variable named WATCHTOWER_HTTP_API_TOKEN. To prevent external services from accidentally triggering image updates, all of the requests have to contain a "Token" field, valued as the token defined in WATCHTOWER_HTTP_API_TOKEN, in their headers. In this case, there is a port bind to the host machine, allowing to request localhost:8080 to reach Watchtower. The following `curl` command would trigger an image update: Notice that there is an environment variable named WATCHTOWER_HTTP_API_TOKEN. To prevent external services from accidentally triggering image updates, all of the requests have to contain a "Token" field, valued as the token defined in WATCHTOWER_HTTP_API_TOKEN, in their headers. In this case, there is a port bind to the host machine, allowing to request localhost:8080 to reach Watchtower. The following `curl` command would trigger an image update:
```bash ```bash

@ -151,6 +151,11 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"", "",
viper.GetString("WATCHTOWER_HTTP_API_TOKEN"), viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
"Sets an authentication token to HTTP API requests.") "Sets an authentication token to HTTP API requests.")
flags.BoolP(
"http-api-periodic-polls",
"",
viper.GetBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"),
"Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled")
// https://no-color.org/ // https://no-color.org/
flags.BoolP( flags.BoolP(
"no-color", "no-color",

@ -79,3 +79,18 @@ func testGetSecretsFromFiles(t *testing.T, flagName string, expected string) {
assert.Equal(t, expected, value) assert.Equal(t, expected, value)
} }
func TestHTTPAPIPeriodicPollsFlag(t *testing.T) {
cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd)
RegisterSystemFlags(cmd)
err := cmd.ParseFlags([]string{"--http-api-periodic-polls"})
require.NoError(t, err)
periodicPolls, err := cmd.PersistentFlags().GetBool("http-api-periodic-polls")
require.NoError(t, err)
assert.Equal(t, true, periodicPolls)
}

@ -13,9 +13,13 @@ var (
) )
// New is a factory function creating a new Handler instance // New is a factory function creating a new Handler instance
func New(updateFn func()) *Handler { func New(updateFn func(), updateLock chan bool) *Handler {
if updateLock != nil {
lock = updateLock
} else {
lock = make(chan bool, 1) lock = make(chan bool, 1)
lock <- true lock <- true
}
return &Handler{ return &Handler{
fn: updateFn, fn: updateFn,

Loading…
Cancel
Save