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 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 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 `pre-update` command), and a command that will be executed after restarting the
container (a `post-update` command). container (a `post-update` command). These commands are specified using the
*com.centurylinklabs.watchtower.pre-update-command* and the
These commands are specified using the *com.centurylinklabs.watchtower.pre-update-command* *com.centurylinklabs.watchtower.post-update-command* labels.
and the *com.centurylinklabs.watchtower.post-update-command* labels. The values
of these labels are JSON formatted strings that describes the commands.
These labels can be declared as instructions in a Dockerfile: These labels can be declared as instructions in a Dockerfile:
```docker ```docker
LABEL com.centurylinklabs.watchtower.pre-update-command='{"Cmd": ["sh", "-c", "/dump-data.sh"]}' LABEL com.centurylinklabs.watchtower.pre-update-command="/dump-data.sh"
LABEL com.centurylinklabs.watchtower.post-update-command='{"Cmd": ["sh", "-c", "/restore-data.sh"]}' LABEL com.centurylinklabs.watchtower.post-update-command="/restore-data.sh"
``` ```
Or be specified as part of the `docker run` command line: Or be specified as part of the `docker run` command line:
```bash ```bash
docker run -d \ docker run -d \
--label=com.centurylinklabs.watchtower.pre-update-command='{"Cmd": ["sh", "-c", "/dump-data.sh"]}' \ --label=com.centurylinklabs.watchtower.pre-update-command="/dump-data.sh" \
--label=com.centurylinklabs.watchtower.post-update-command='{"Cmd": ["sh", "-c", "/restore-data.sh"]}' \ --label=com.centurylinklabs.watchtower.post-update-command="/restore-data.sh" \
someimage 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 { if container.Stale {
// Execute the pre-update command if it is defined. // Execute the pre-update command if it is defined.
preUpdateCommandInfo, err := container.PreUpdateCommandInfo() preUpdateCommand := container.PreUpdateCommand()
if err != nil { if len(preUpdateCommand) > 0 {
log.Error("Error while reading pre-update command info.")
log.Error(err)
} else if preUpdateCommandInfo.IsDefined() {
log.Info("Executing pre-update command.") 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) log.Error(err)
} }
} }
@ -87,13 +84,10 @@ func Update(client container.Client, filter container.Filter, cleanup bool, noRe
log.Error(err) log.Error(err)
} else { } else {
// Execute the post-update command if it is defined. // Execute the post-update command if it is defined.
postUpdateCommandInfo, err := container.PostUpdateCommandInfo() postUpdateCommand := container.PostUpdateCommand()
if err != nil { if len(postUpdateCommand) > 0 {
log.Error("Error while reading post-update command info.")
log.Error(err)
} else if postUpdateCommandInfo.IsDefined() {
log.Info("Executing post-update command.") 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) log.Error(err)
} }
} }

View file

@ -25,7 +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 ExecuteCommand(Container, string) 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
@ -223,7 +223,7 @@ func (client dockerClient) IsContainerStale(c Container) (bool, error) {
return false, nil return false, nil
} }
func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo) error { func (client dockerClient) ExecuteCommand(c Container, command string) error {
bg := context.Background() bg := context.Background()
// Get the id of the actual container // Get the id of the actual container
@ -235,14 +235,11 @@ func (client dockerClient) ExecuteCommand(c Container, commandInfo CommandInfo)
// Create the exec // Create the exec
execConfig := types.ExecConfig{ execConfig := types.ExecConfig{
User: commandInfo.User,
Privileged: commandInfo.Privileged,
Tty: true, Tty: true,
AttachStderr: true, AttachStderr: true,
AttachStdout: true, AttachStdout: true,
Detach: false, Detach: false,
Env: commandInfo.Env, Cmd: []string{"sh", "-c", command},
Cmd: commandInfo.Cmd,
} }
exec, err := client.api.ContainerExecCreate(bg, containerID, execConfig) exec, err := client.api.ContainerExecCreate(bg, containerID, execConfig)
if err != nil { 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 // 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) execInspect, err := client.api.ContainerExecInspect(bg, exec.ID)
if err != nil { if err != nil {
return err return err
} }
if execInspect.ExitCode > 0 { if execInspect.ExitCode > 0 {
log.Error("Failed to execute command.") log.Errorf("Command exited with code %v.", execInspect.ExitCode)
} }
return nil 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 "" 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. // 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 { if val, ok := c.containerInfo.Config.Labels[preUpdateCommandLabel]; ok {
commandInfo, err := ReadCommandInfoFromJSON(val) return val
if err != nil {
return CommandInfo{}, err
}
return commandInfo, nil
} }
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. // 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 { if val, ok := c.containerInfo.Config.Labels[postUpdateCommandLabel]; ok {
commandInfo, err := ReadCommandInfoFromJSON(val) return val
if err != nil {
return CommandInfo{}, err
}
return commandInfo, nil
} }
return CommandInfo{}, nil return ""
} }
// 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