diff --git a/actions/update.go b/actions/update.go index 4d488a5..6d04fa1 100644 --- a/actions/update.go +++ b/actions/update.go @@ -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) + } + } } } diff --git a/container/client.go b/container/client.go index 476e3ca..98041a6 100644 --- a/container/client.go +++ b/container/client.go @@ -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) @@ -247,4 +294,4 @@ func (client dockerClient) waitForStop(c Container, waitTime time.Duration) erro time.Sleep(1 * time.Second) } -} +} \ No newline at end of file diff --git a/container/command_info.go b/container/command_info.go new file mode 100644 index 0000000..6c02c25 --- /dev/null +++ b/container/command_info.go @@ -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 +} \ No newline at end of file diff --git a/container/container.go b/container/container.go index 7453507..4582a98 100644 --- a/container/container.go +++ b/container/container.go @@ -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 @@ -181,4 +213,4 @@ func (c Container) hostConfig() *dockercontainer.HostConfig { } return hostConfig -} +} \ No newline at end of file