add pre/post update check lifecycle hooks (#373)

* add pre/post update check lifecycle hooks

* update docs for lifecycle hooks

* Fix phrasing
pull/384/head
Niklas Wigertz Danielsson 5 years ago committed by Simon Aronsson
parent 1b3db5ed5d
commit 135467dcf6

@ -1,45 +1,53 @@
## Executing commands before and after updating ## Executing commands before and after updating
> **DO NOTE**: Both commands are shell commands executed with `sh`, and therefore require the > **DO NOTE**: These are shell commands executed with `sh`, and therefore require the
> container to provide the `sh` executable. > container to provide the `sh` executable.
It is possible to execute a *pre-update* command and a *post-update* command It is possible to execute _pre/post\-check_ and _pre/post\-update_ commands
**inside** every container updated by watchtower. The *pre-update* command is **inside** every container updated by watchtower.
executed before stopping the container, and the *post-update* command is
executed after restarting the container. - The _pre-check_ command is executed for each container prior to every update cycle.
- The _pre-update_ command is executed before stopping the container when an update is about to start.
- The _post-update_ command is executed after restarting the updated container
- The _post-check_ command is executed for each container post every update cycle.
This feature is disabled by default. To enable it, you need to set the option This feature is disabled by default. To enable it, you need to set the option
`--enable-lifecycle-hooks` on the command line, or set the environment variable `--enable-lifecycle-hooks` on the command line, or set the environment variable
`WATCHTOWER_LIFECYCLE_HOOKS` to `true`. `WATCHTOWER_LIFECYCLE_HOOKS` to `true`.
### Specifying update commands ### Specifying update commands
The commands are specified using docker container labels, with The commands are specified using docker container labels, the following are currently available:
`com.centurylinklabs.watchtower.lifecycle.pre-update-command` for the *pre-update*
command and `com.centurylinklabs.watchtower.lifecycle.post-update` for the | Type | Docker Container Label |
*post-update* command. | ----------- | ------------------------------------------------------ |
| Pre Check | `com.centurylinklabs.watchtower.lifecycle.pre-check` |
| Pre Update | `com.centurylinklabs.watchtower.lifecycle.pre-update` |
| Post Update | `com.centurylinklabs.watchtower.lifecycle.post-update` |
| Post Check | `com.centurylinklabs.watchtower.lifecycle.post-check` |
These labels can be declared as instructions in a Dockerfile: These labels can be declared as instructions in a Dockerfile (with some example .sh files):
```docker ```docker
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh"
``` ```
Or be specified as part of the `docker run` command line: Or be specified as part of the `docker run` command line:
```bash ```bash
docker run -d \ docker run -d \
--label=com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" \ --label=com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" \ --label=com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" \
someimage someimage
--label=com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh" \
``` ```
### Execution failure ### Execution failure
The failure of a command to execute, identified by an exit code different than The failure of a command to execute, identified by an exit code different than
0, will not prevent watchtower from updating the container. Only an error 0, will not prevent watchtower from updating the container. Only an error
log statement containing the exit code will be reported. log statement containing the exit code will be reported.

@ -13,6 +13,8 @@ import (
func Update(client container.Client, params UpdateParams) error { func Update(client container.Client, params UpdateParams) error {
log.Debug("Checking containers for updated images") log.Debug("Checking containers for updated images")
executePreCheck(client, params)
containers, err := client.ListContainers(params.Filter) containers, err := client.ListContainers(params.Filter)
if err != nil { if err != nil {
return err return err
@ -36,12 +38,14 @@ func Update(client container.Client, params UpdateParams) error {
checkDependencies(containers) checkDependencies(containers)
if params.MonitorOnly { if params.MonitorOnly {
executePostCheck(client, params)
return nil return nil
} }
stopContainersInReversedOrder(containers, client, params) stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params) restartContainersInSortedOrder(containers, client, params)
executePostCheck(client, params)
return nil return nil
} }
@ -123,11 +127,58 @@ func checkDependencies(containers []container.Container) {
} }
} }
func executePreCheck(client container.Client, params UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
executePreCheckCommand(client, container)
}
}
func executePostCheck(client container.Client, params UpdateParams) {
containers, err := client.ListContainers(params.Filter)
if err != nil {
return
}
for _, container := range containers {
executePostCheckCommand(client, container)
}
}
func executePreCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreCheckCommand()
if len(command) == 0 {
log.Debug("No pre-check command supplied. Skipping")
return
}
log.Info("Executing pre-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
func executePostCheckCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePostCheckCommand()
if len(command) == 0 {
log.Debug("No post-check command supplied. Skipping")
return
}
log.Info("Executing post-check command.")
if err := client.ExecuteCommand(container.ID(), command); err != nil {
log.Error(err)
}
}
func executePreUpdateCommand(client container.Client, container container.Container) { func executePreUpdateCommand(client container.Client, container container.Container) {
command := container.GetLifecyclePreUpdateCommand() command := container.GetLifecyclePreUpdateCommand()
if len(command) == 0 { if len(command) == 0 {
log.Debug("No pre-update command supplied. Skipping") log.Debug("No pre-update command supplied. Skipping")
return
} }
log.Info("Executing pre-update command.") log.Info("Executing pre-update command.")
@ -146,6 +197,7 @@ func executePostUpdateCommand(client container.Client, newContainerID string) {
command := newContainer.GetLifecyclePostUpdateCommand() command := newContainer.GetLifecyclePostUpdateCommand()
if len(command) == 0 { if len(command) == 0 {
log.Debug("No post-update command supplied. Skipping") log.Debug("No post-update command supplied. Skipping")
return
} }
log.Info("Executing post-update command.") log.Info("Executing post-update command.")

@ -5,10 +5,22 @@ const (
signalLabel = "com.centurylinklabs.watchtower.stop-signal" signalLabel = "com.centurylinklabs.watchtower.stop-signal"
enableLabel = "com.centurylinklabs.watchtower.enable" enableLabel = "com.centurylinklabs.watchtower.enable"
zodiacLabel = "com.centurylinklabs.zodiac.original-image" zodiacLabel = "com.centurylinklabs.zodiac.original-image"
preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update" preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update" postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
) )
// GetLifecyclePreCheckCommand returns the pre-check command set in the container metadata or an empty string
func (c Container) GetLifecyclePreCheckCommand() string {
return c.getLabelValueOrEmpty(preCheckLabel)
}
// GetLifecyclePostCheckCommand returns the post-check command set in the container metadata or an empty string
func (c Container) GetLifecyclePostCheckCommand() string {
return c.getLabelValueOrEmpty(postCheckLabel)
}
// GetLifecyclePreUpdateCommand returns the pre-update command set in the container metadata or an empty string // GetLifecyclePreUpdateCommand returns the pre-update command set in the container metadata or an empty string
func (c Container) GetLifecyclePreUpdateCommand() string { func (c Container) GetLifecyclePreUpdateCommand() string {
return c.getLabelValueOrEmpty(preUpdateLabel) return c.getLabelValueOrEmpty(preUpdateLabel)

Loading…
Cancel
Save