mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-16 15:10:12 +01:00
Implement pre and post-update command execution.
This commit is contained in:
parent
8197bb669d
commit
60d47a5749
4 changed files with 137 additions and 6 deletions
|
|
@ -49,6 +49,19 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Stale {
|
if container.Stale {
|
||||||
|
|
||||||
|
// Execute the pre-update command if it is defined.
|
||||||
|
preUpdateCommandInfo, err := container.PreUpdateCommandInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error while reading pre-update command info.")
|
||||||
|
log.Error(err)
|
||||||
|
} else if preUpdateCommandInfo.IsDefined() {
|
||||||
|
log.Info("Executing pre-update command.")
|
||||||
|
if err := client.ExecuteCommand(container, preUpdateCommandInfo); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := client.StopContainer(container, timeout); err != nil {
|
if err := client.StopContainer(container, timeout); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +85,18 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
|
||||||
if !noRestart {
|
if !noRestart {
|
||||||
if err := client.StartContainer(container); err != nil {
|
if err := client.StartContainer(container); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
} else {
|
||||||
|
// Execute the post-update command if it is defined.
|
||||||
|
postUpdateCommandInfo, err := container.PostUpdateCommandInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error while reading post-update command info.")
|
||||||
|
log.Error(err)
|
||||||
|
} else if postUpdateCommandInfo.IsDefined() {
|
||||||
|
log.Info("Executing post-update command.")
|
||||||
|
if err := client.ExecuteCommand(container, postUpdateCommandInfo); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ type Client interface {
|
||||||
RenameContainer(Container, string) error
|
RenameContainer(Container, string) error
|
||||||
IsContainerStale(Container) (bool, error)
|
IsContainerStale(Container) (bool, error)
|
||||||
RemoveImage(Container) error
|
RemoveImage(Container) error
|
||||||
|
ExecuteCommand(Container, CommandInfo) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a new Client instance which can be used to interact with
|
// NewClient returns a new Client instance which can be used to interact with
|
||||||
|
|
@ -222,6 +223,52 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo) error {
|
||||||
|
bg := context.Background()
|
||||||
|
|
||||||
|
// Get the id of the actual container
|
||||||
|
containerJSON, err := client.api.ContainerInspect(bg, c.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerID := containerJSON.ID
|
||||||
|
|
||||||
|
// Create the exec
|
||||||
|
execConfig := types.ExecConfig{
|
||||||
|
User: commandInfo.User,
|
||||||
|
Privileged: commandInfo.Privileged,
|
||||||
|
Tty: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
AttachStdout: true,
|
||||||
|
Detach: false,
|
||||||
|
Env: commandInfo.Env,
|
||||||
|
Cmd: commandInfo.Cmd,
|
||||||
|
}
|
||||||
|
exec, err := client.api.ContainerExecCreate(bg, containerID, execConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the exec
|
||||||
|
execStartCheck := types.ExecStartCheck{Detach: false, Tty: true}
|
||||||
|
err = client.api.ContainerExecStart(bg, exec.ID, execStartCheck)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect the exec to get the exit code and print a message if the
|
||||||
|
// exit code is not success
|
||||||
|
execInspect, err := client.api.ContainerExecInspect(bg, exec.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if execInspect.ExitCode > 0 {
|
||||||
|
log.Error("Failed to execute command.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (client dockerClient) RemoveImage(c Container) error {
|
func (client dockerClient) RemoveImage(c Container) error {
|
||||||
imageID := c.ImageID()
|
imageID := c.ImageID()
|
||||||
log.Infof("Removing image %s", imageID)
|
log.Infof("Removing image %s", imageID)
|
||||||
|
|
@ -247,4 +294,4 @@ func (client dockerClient) waitForStop(c Container, waitTime time.Duration) erro
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
27
container/command_info.go
Normal file
27
container/command_info.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandInfo struct {
|
||||||
|
User string
|
||||||
|
Privileged bool
|
||||||
|
Env []string
|
||||||
|
Cmd []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadCommandInfoFromJSON(commandInfoJSON string) (CommandInfo, error) {
|
||||||
|
commandInfo := CommandInfo{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(commandInfoJSON), &commandInfo)
|
||||||
|
if err != nil {
|
||||||
|
return CommandInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (commandInfo CommandInfo) IsDefined() bool {
|
||||||
|
return commandInfo.Cmd != nil && len(commandInfo.Cmd) > 0
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||||
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"
|
||||||
|
preUpdateCommandLabel = "com.centurylinklabs.watchtower.pre-update-command"
|
||||||
|
postUpdateCommandLabel = "com.centurylinklabs.watchtower.post-update-command"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewContainer returns a new Container instance instantiated with the
|
// NewContainer returns a new Container instance instantiated with the
|
||||||
|
|
@ -117,6 +119,36 @@ func (c Container) StopSignal() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreUpdateCommand returns the pre-update command set in the container's
|
||||||
|
// metadata or an empty string if the user did not set it.
|
||||||
|
func (c Container) PreUpdateCommandInfo() (CommandInfo, error) {
|
||||||
|
if val, ok := c.containerInfo.Config.Labels[preUpdateCommandLabel]; ok {
|
||||||
|
|
||||||
|
commandInfo, err := ReadCommandInfoFromJSON(val)
|
||||||
|
if err != nil {
|
||||||
|
return CommandInfo{}, err
|
||||||
|
}
|
||||||
|
return commandInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostUpdateCommand returns the post-update command set in the container's
|
||||||
|
// metadata or an empty string if the user did not set it.
|
||||||
|
func (c Container) PostUpdateCommandInfo() (CommandInfo, error) {
|
||||||
|
if val, ok := c.containerInfo.Config.Labels[postUpdateCommandLabel]; ok {
|
||||||
|
|
||||||
|
commandInfo, err := ReadCommandInfoFromJSON(val)
|
||||||
|
if err != nil {
|
||||||
|
return CommandInfo{}, err
|
||||||
|
}
|
||||||
|
return commandInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Ideally, we'd just be able to take the ContainerConfig from the old container
|
// Ideally, we'd just be able to take the ContainerConfig from the old container
|
||||||
// and use it as the starting point for creating the new container; however,
|
// and use it as the starting point for creating the new container; however,
|
||||||
// the ContainerConfig that comes back from the Inspect call merges the default
|
// the ContainerConfig that comes back from the Inspect call merges the default
|
||||||
|
|
@ -181,4 +213,4 @@ func (c Container) hostConfig() *dockercontainer.HostConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
return hostConfig
|
return hostConfig
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue