Use simple string instead of JSON string for pre/post update commands.

This commit is contained in:
Alexandre Menif 2018-06-18 19:37:32 +02:00
parent 25c7853f03
commit 78a375f86e
5 changed files with 26 additions and 88 deletions

View file

@ -280,35 +280,23 @@ docker run -d \
For every container that could be updated by watchtower, it is possible to
specify a command that will be executed before stopping the container (a
`pre-update` command), and a command that will be executed after restarting the
container (a `post-update` command).
These commands are specified using the *com.centurylinklabs.watchtower.pre-update-command*
and the *com.centurylinklabs.watchtower.post-update-command* labels. The values
of these labels are JSON formatted strings that describes the commands.
container (a `post-update` command). These commands are specified using the
*com.centurylinklabs.watchtower.pre-update-command* and the
*com.centurylinklabs.watchtower.post-update-command* labels.
These labels can be declared as instructions in a Dockerfile:
```docker
LABEL com.centurylinklabs.watchtower.pre-update-command='{"Cmd": ["sh", "-c", "/dump-data.sh"]}'
LABEL com.centurylinklabs.watchtower.post-update-command='{"Cmd": ["sh", "-c", "/restore-data.sh"]}'
LABEL com.centurylinklabs.watchtower.pre-update-command="/dump-data.sh"
LABEL com.centurylinklabs.watchtower.post-update-command="/restore-data.sh"
```
Or be specified as part of the `docker run` command line:
```bash
docker run -d \
--label=com.centurylinklabs.watchtower.pre-update-command='{"Cmd": ["sh", "-c", "/dump-data.sh"]}' \
--label=com.centurylinklabs.watchtower.post-update-command='{"Cmd": ["sh", "-c", "/restore-data.sh"]}' \
--label=com.centurylinklabs.watchtower.pre-update-command="/dump-data.sh" \
--label=com.centurylinklabs.watchtower.post-update-command="/restore-data.sh" \
someimage
```
The JSON object is made of the following fields:
* `Cmd`: the command to execute, as an array of strings (required)
* `User`: a string representing a username or a UID. It is of the form `user`,
`user:group`, `uid`, or `uid:gid` (optional)
* `Privileged`: `true` if the command should be given extended privileges,
`false` otherwise (optional, default to `false`)
* `Env`: a list of instructions to set enviroment variables, for example
`["FOO=bar", BAZ=quux]` (optional)

View file

@ -51,13 +51,10 @@ 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() {
preUpdateCommand := container.PreUpdateCommand()
if len(preUpdateCommand) > 0 {
log.Info("Executing pre-update command.")
if err := client.ExecuteCommand(container, preUpdateCommandInfo); err != nil {
if err := client.ExecuteCommand(container, preUpdateCommand); err != nil {
log.Error(err)
}
}
@ -87,13 +84,10 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
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() {
postUpdateCommand := container.PostUpdateCommand()
if len(postUpdateCommand) > 0 {
log.Info("Executing post-update command.")
if err := client.ExecuteCommand(container, postUpdateCommandInfo); err != nil {
if err := client.ExecuteCommand(container, postUpdateCommand); err != nil {
log.Error(err)
}
}

View file

@ -25,7 +25,7 @@ type Client interface {
RenameContainer(Container, string) error
IsContainerStale(Container) (bool, error)
RemoveImage(Container) error
ExecuteCommand(Container, CommandInfo) error
ExecuteCommand(Container, string) error
}
// NewClient returns a new Client instance which can be used to interact with
@ -223,7 +223,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return false, nil
}
func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo) error {
func (client dockerClient) ExecuteCommand(c Container, command string) error {
bg := context.Background()
// Get the id of the actual container
@ -235,14 +235,11 @@ func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo)
// 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,
Cmd: []string{"sh", "-c", command},
}
exec, err := client.api.ContainerExecCreate(bg, containerID, execConfig)
if err != nil {
@ -257,13 +254,13 @@ func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo)
}
// Inspect the exec to get the exit code and print a message if the
// exit code is not success
// 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.")
log.Errorf("Command exited with code %v.", execInspect.ExitCode)
}
return nil

View file

@ -1,33 +0,0 @@
package container
import (
"encoding/json"
)
// CommandInfo is the data structure used to encode information about a
// pre/post-update command.
type CommandInfo struct {
User string
Privileged bool
Env []string
Cmd []string
}
// ReadCommandInfoFromJSON takes a JSON formatted description of a
// pre/post-update as input and returns the parsed data as a CommandInfo.
func ReadCommandInfoFromJSON(commandInfoJSON string) (CommandInfo, error) {
commandInfo := CommandInfo{}
err := json.Unmarshal([]byte(commandInfoJSON), &commandInfo)
if err != nil {
return CommandInfo{}, err
}
return commandInfo, nil
}
// IsDefined returns true if a CommandInfo actually contains a command to
// execute or false otherwise.
func (commandInfo CommandInfo) IsDefined() bool {
return commandInfo.Cmd != nil && len(commandInfo.Cmd) > 0
}

View file

@ -119,34 +119,26 @@ func (c Container) StopSignal() string {
return ""
}
// PreUpdateCommandInfo returns the pre-update command set in the container's
// 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) {
func (c Container) PreUpdateCommand() string {
if val, ok := c.containerInfo.Config.Labels[preUpdateCommandLabel]; ok {
commandInfo, err := ReadCommandInfoFromJSON(val)
if err != nil {
return CommandInfo{}, err
}
return commandInfo, nil
return val
}
return CommandInfo{}, nil
return ""
}
// PostUpdateCommandInfo returns the post-update command set in the container's
// 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) {
func (c Container) PostUpdateCommand() string {
if val, ok := c.containerInfo.Config.Labels[postUpdateCommandLabel]; ok {
commandInfo, err := ReadCommandInfoFromJSON(val)
if err != nil {
return CommandInfo{}, err
}
return commandInfo, nil
return val
}
return CommandInfo{}, nil
return ""
}
// Ideally, we'd just be able to take the ContainerConfig from the old container