mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00

add unit tests for the check action to allow for some refactoring and bug fixing without having to worry about breaking stuff. resolve watchtower cleanup bug by adding an initial 1 second sleep in the check action. without the sleep, the docker client returns an empty array, which is why we were left with two watchtowers.
190 lines
5.5 KiB
Go
190 lines
5.5 KiB
Go
package container
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
dockercontainer "github.com/docker/docker/api/types/container"
|
|
)
|
|
|
|
const (
|
|
watchtowerLabel = "com.centurylinklabs.watchtower"
|
|
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
|
enableLabel = "com.centurylinklabs.watchtower.enable"
|
|
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
|
)
|
|
|
|
// NewContainer returns a new Container instance instantiated with the
|
|
// specified ContainerInfo and ImageInfo structs.
|
|
func NewContainer(containerInfo *types.ContainerJSON, imageInfo *types.ImageInspect) *Container {
|
|
return &Container{
|
|
containerInfo: containerInfo,
|
|
imageInfo: imageInfo,
|
|
}
|
|
}
|
|
|
|
// Container represents a running Docker container.
|
|
type Container struct {
|
|
Stale bool
|
|
|
|
containerInfo *types.ContainerJSON
|
|
imageInfo *types.ImageInspect
|
|
}
|
|
|
|
// ID returns the Docker container ID.
|
|
func (c Container) ID() string {
|
|
return c.containerInfo.ID
|
|
}
|
|
|
|
// Name returns the Docker container name.
|
|
func (c Container) Name() string {
|
|
return c.containerInfo.Name
|
|
}
|
|
|
|
// ImageID returns the ID of the Docker image that was used to start the
|
|
// container.
|
|
func (c Container) ImageID() string {
|
|
return c.imageInfo.ID
|
|
}
|
|
|
|
// ImageName returns the name of the Docker image that was used to start the
|
|
// container. If the original image was specified without a particular tag, the
|
|
// "latest" tag is assumed.
|
|
func (c Container) ImageName() string {
|
|
// Compatibility w/ Zodiac deployments
|
|
imageName, ok := c.containerInfo.Config.Labels[zodiacLabel]
|
|
if !ok {
|
|
imageName = c.containerInfo.Config.Image
|
|
}
|
|
|
|
if !strings.Contains(imageName, ":") {
|
|
imageName = fmt.Sprintf("%s:latest", imageName)
|
|
}
|
|
|
|
return imageName
|
|
}
|
|
|
|
// Enabled returns the value of the container enabled label and if the label
|
|
// was set.
|
|
func (c Container) Enabled() (bool, bool) {
|
|
rawBool, ok := c.containerInfo.Config.Labels[enableLabel]
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
|
|
parsedBool, err := strconv.ParseBool(rawBool)
|
|
if err != nil {
|
|
return false, false
|
|
}
|
|
|
|
return parsedBool, true
|
|
}
|
|
|
|
// Links returns a list containing the names of all the containers to which
|
|
// this container is linked.
|
|
func (c Container) Links() []string {
|
|
var links []string
|
|
|
|
if (c.containerInfo != nil) && (c.containerInfo.HostConfig != nil) {
|
|
for _, link := range c.containerInfo.HostConfig.Links {
|
|
name := strings.Split(link, ":")[0]
|
|
links = append(links, name)
|
|
}
|
|
}
|
|
|
|
return links
|
|
}
|
|
|
|
// IsWatchtower returns a boolean flag indicating whether or not the current
|
|
// container is the watchtower container itself. The watchtower container is
|
|
// identified by the presence of the "com.centurylinklabs.watchtower" label in
|
|
// the container metadata.
|
|
func (c Container) IsWatchtower() bool {
|
|
return ContainsWatchtowerLabel(c.containerInfo.Config.Labels)
|
|
}
|
|
|
|
// StopSignal returns the custom stop signal (if any) that is encoded in the
|
|
// container's metadata. If the container has not specified a custom stop
|
|
// signal, the empty string "" is returned.
|
|
func (c Container) StopSignal() string {
|
|
if val, ok := c.containerInfo.Config.Labels[signalLabel]; ok {
|
|
return val
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// 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
|
|
// configuration (the stuff specified in the metadata for the image itself)
|
|
// with the overridden configuration (the stuff that you might specify as part
|
|
// of the "docker run"). In order to avoid unintentionally overriding the
|
|
// defaults in the new image we need to separate the override options from the
|
|
// default options. To do this we have to compare the ContainerConfig for the
|
|
// running container with the ContainerConfig from the image that container was
|
|
// started from. This function returns a ContainerConfig which contains just
|
|
// the options overridden at runtime.
|
|
func (c Container) runtimeConfig() *dockercontainer.Config {
|
|
config := c.containerInfo.Config
|
|
imageConfig := c.imageInfo.Config
|
|
|
|
if config.WorkingDir == imageConfig.WorkingDir {
|
|
config.WorkingDir = ""
|
|
}
|
|
|
|
if config.User == imageConfig.User {
|
|
config.User = ""
|
|
}
|
|
|
|
if sliceEqual(config.Cmd, imageConfig.Cmd) {
|
|
config.Cmd = nil
|
|
}
|
|
|
|
if sliceEqual(config.Entrypoint, imageConfig.Entrypoint) {
|
|
config.Entrypoint = nil
|
|
}
|
|
|
|
config.Env = sliceSubtract(config.Env, imageConfig.Env)
|
|
|
|
config.Labels = stringMapSubtract(config.Labels, imageConfig.Labels)
|
|
|
|
config.Volumes = structMapSubtract(config.Volumes, imageConfig.Volumes)
|
|
|
|
// subtract ports exposed in image from container
|
|
for k := range config.ExposedPorts {
|
|
if _, ok := imageConfig.ExposedPorts[k]; ok {
|
|
delete(config.ExposedPorts, k)
|
|
}
|
|
}
|
|
for p := range c.containerInfo.HostConfig.PortBindings {
|
|
config.ExposedPorts[p] = struct{}{}
|
|
}
|
|
|
|
config.Image = c.ImageName()
|
|
return config
|
|
}
|
|
|
|
// Any links in the HostConfig need to be re-written before they can be
|
|
// re-submitted to the Docker create API.
|
|
func (c Container) hostConfig() *dockercontainer.HostConfig {
|
|
hostConfig := c.containerInfo.HostConfig
|
|
|
|
for i, link := range hostConfig.Links {
|
|
name := link[0:strings.Index(link, ":")]
|
|
alias := link[strings.LastIndex(link, "/"):]
|
|
|
|
hostConfig.Links[i] = fmt.Sprintf("%s:%s", name, alias)
|
|
}
|
|
|
|
return hostConfig
|
|
}
|
|
|
|
// ContainsWatchtowerLabel takes a map of labels and values and tells
|
|
// the consumer whether it contains a valid watchtower instance label
|
|
func ContainsWatchtowerLabel(labels map[string]string) bool {
|
|
val, ok := labels[watchtowerLabel]
|
|
return ok && val == "true"
|
|
}
|