Implement pre and post-update command execution.

This commit is contained in:
Alexandre Menif 2018-05-27 20:59:53 +02:00
parent 8197bb669d
commit 60d47a5749
4 changed files with 137 additions and 6 deletions

View file

@ -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)
}
}
} }
} }

View file

@ -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
View 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
}

View file

@ -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
} }