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 } // IsRunning returns a boolean flag indicating whether or not the current // container is running. The status is determined by the value of the // container's "State.Running" property. func (c Container) IsRunning() bool { return c.containerInfo.State.Running } // 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" }