Skip to content

Commit 462df02

Browse files
authored
Merge pull request #39 from stffabi/upstream_schedule
Simple Scheduling for Upgrades (Maintenance Window)
2 parents 5902e9e + 525dfea commit 462df02

File tree

2 files changed

+59
-25
lines changed

2 files changed

+59
-25
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ docker run --rm v2tec/watchtower --help
7373

7474
* `--host, -h` Docker daemon socket to connect to. Defaults to "unix:///var/run/docker.sock" but can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port". The host value can also be provided by setting the `DOCKER_HOST` environment variable.
7575
* `--interval, -i` Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Defaults to 300 seconds (5 minutes).
76+
* `--schedule, -s` [Cron expression](https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format) which defines when and how often to check for new images. Either `--interval` or the schedule expression could be defined, but not both.
7677
* `--no-pull` Do not pull new images. When this flag is specified, watchtower will not attempt to pull new images from the registry. Instead it will only monitor the local image cache for changes. Use this option if you are building new images directly on the Docker host without pushing them to a registry.
7778
* `--cleanup` Remove old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
7879
* `--tlsverify` Use TLS when connecting to the Docker socket and verify the server's certificate.

main.go

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ import (
44
"fmt"
55
"os"
66
"os/signal"
7-
"sync"
87
"syscall"
98
"time"
109

10+
"strconv"
11+
1112
log "github.com/Sirupsen/logrus"
13+
"github.com/robfig/cron"
1214
"github.com/urfave/cli"
1315
"github.com/v2tec/watchtower/actions"
1416
"github.com/v2tec/watchtower/container"
1517
)
1618

1719
var (
18-
wg sync.WaitGroup
1920
client container.Client
20-
pollInterval time.Duration
21+
scheduleSpec string
2122
cleanup bool
2223
noRestart bool
2324
)
@@ -45,6 +46,11 @@ func main() {
4546
Value: 300,
4647
EnvVar: "WATCHTOWER_POLL_INTERVAL",
4748
},
49+
cli.StringFlag{
50+
Name: "schedule, s",
51+
Usage: "the cron expression which defines when to update",
52+
EnvVar: "WATCHTOWER_SCHEDULE",
53+
},
4854
cli.BoolFlag{
4955
Name: "no-pull",
5056
Usage: "do not pull new images",
@@ -86,7 +92,17 @@ func before(c *cli.Context) error {
8692
log.SetLevel(log.DebugLevel)
8793
}
8894

89-
pollInterval = time.Duration(c.Int("interval")) * time.Second
95+
pollingSet := c.IsSet("interval")
96+
cronSet := c.IsSet("schedule")
97+
98+
if pollingSet && cronSet {
99+
log.Fatal("Only schedule or interval can be defined, not both.")
100+
} else if cronSet {
101+
scheduleSpec = c.String("schedule")
102+
} else {
103+
scheduleSpec = "@every " + strconv.Itoa(c.Int("interval")) + "s"
104+
}
105+
90106
cleanup = c.GlobalBool("cleanup")
91107
noRestart = c.GlobalBool("no-restart")
92108

@@ -97,40 +113,57 @@ func before(c *cli.Context) error {
97113
}
98114

99115
client = container.NewClient(!c.GlobalBool("no-pull"))
100-
101-
handleSignals()
102116
return nil
103117
}
104118

105-
func start(c *cli.Context) {
119+
func start(c *cli.Context) error {
106120
names := c.Args()
107121

108122
if err := actions.CheckPrereqs(client, cleanup); err != nil {
109123
log.Fatal(err)
110124
}
111125

112-
for {
113-
wg.Add(1)
114-
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
115-
fmt.Println(err)
116-
}
117-
wg.Done()
126+
tryLockSem := make(chan bool, 1)
127+
tryLockSem <- true
128+
129+
cron := cron.New()
130+
err := cron.AddFunc(
131+
scheduleSpec,
132+
func() {
133+
select {
134+
case v := <-tryLockSem:
135+
defer func() { tryLockSem <- v }()
136+
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
137+
fmt.Println(err)
138+
}
139+
default:
140+
log.Debug("Skipped another update already running.")
141+
}
142+
143+
nextRuns := cron.Entries()
144+
if len(nextRuns) > 0 {
145+
log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
146+
}
147+
})
118148

119-
time.Sleep(pollInterval)
149+
if err != nil {
150+
return err
120151
}
121-
}
122152

123-
func handleSignals() {
153+
log.Info("First run: " + cron.Entries()[0].Schedule.Next(time.Now()).String())
154+
cron.Start()
155+
124156
// Graceful shut-down on SIGINT/SIGTERM
125-
c := make(chan os.Signal, 1)
126-
signal.Notify(c, os.Interrupt)
127-
signal.Notify(c, syscall.SIGTERM)
128-
129-
go func() {
130-
<-c
131-
wg.Wait()
132-
os.Exit(1)
133-
}()
157+
interrupt := make(chan os.Signal, 1)
158+
signal.Notify(interrupt, os.Interrupt)
159+
signal.Notify(interrupt, syscall.SIGTERM)
160+
161+
<-interrupt
162+
cron.Stop()
163+
log.Info("Waiting for running update to be finished...")
164+
<-tryLockSem
165+
os.Exit(1)
166+
return nil
134167
}
135168

136169
func setEnvOptStr(env string, opt string) error {

0 commit comments

Comments
 (0)