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 {
|
||||
|
||||
// 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 {
|
||||
log.Error(err)
|
||||
}
|
||||
|
|
@ -72,6 +85,18 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
|
|||
if !noRestart {
|
||||
if err := client.StartContainer(container); err != nil {
|
||||
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
|
||||
IsContainerStale(Container) (bool, error)
|
||||
RemoveImage(Container) error
|
||||
ExecuteCommand(Container, CommandInfo) error
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
imageID := c.ImageID()
|
||||
log.Infof("Removing image %s", imageID)
|
||||
|
|
|
|||
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 (
|
||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
||||
enableLabel = "com.centurylinklabs.watchtower.enable"
|
||||
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
||||
enableLabel = "com.centurylinklabs.watchtower.enable"
|
||||
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
|
||||
|
|
@ -117,6 +119,36 @@ func (c Container) StopSignal() string {
|
|||
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
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue