2022-11-01 00:00:00 +01:00
// Package container contains code related to dealing with docker containers
2015-07-21 16:04:41 +00:00
package container
2015-07-14 20:51:12 +00:00
import (
"fmt"
2017-10-17 06:53:30 +02:00
"strconv"
2015-07-14 20:51:12 +00:00
"strings"
2019-04-20 16:44:41 +02:00
2019-11-25 21:11:12 +01:00
"github.com/containrrr/watchtower/internal/util"
2021-06-27 09:05:01 +02:00
wt "github.com/containrrr/watchtower/pkg/types"
2019-11-25 21:11:12 +01:00
2016-10-13 21:34:24 +01:00
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
2022-01-22 17:40:37 +01:00
"github.com/docker/go-connections/nat"
2015-07-14 20:51:12 +00:00
)
2015-07-31 22:23:17 +00:00
// NewContainer returns a new Container instance instantiated with the
// specified ContainerInfo and ImageInfo structs.
2016-10-13 21:34:24 +01:00
func NewContainer ( containerInfo * types . ContainerJSON , imageInfo * types . ImageInspect ) * Container {
2015-07-21 19:37:18 +00:00
return & Container {
containerInfo : containerInfo ,
imageInfo : imageInfo ,
}
}
2015-07-31 22:23:17 +00:00
// Container represents a running Docker container.
2015-07-14 20:51:12 +00:00
type Container struct {
2021-04-18 18:37:35 +02:00
LinkedToRestarting bool
Stale bool
2015-07-14 20:51:12 +00:00
2016-10-13 21:34:24 +01:00
containerInfo * types . ContainerJSON
imageInfo * types . ImageInspect
2015-07-14 20:51:12 +00:00
}
2023-04-12 17:36:01 +02:00
// IsLinkedToRestarting returns the current value of the LinkedToRestarting field for the container
func ( c * Container ) IsLinkedToRestarting ( ) bool {
return c . LinkedToRestarting
}
// IsStale returns the current value of the Stale field for the container
func ( c * Container ) IsStale ( ) bool {
return c . Stale
}
// SetLinkedToRestarting sets the LinkedToRestarting field for the container
func ( c * Container ) SetLinkedToRestarting ( value bool ) {
c . LinkedToRestarting = value
}
// SetStale implements sets the Stale field for the container
func ( c * Container ) SetStale ( value bool ) {
c . Stale = value
}
2020-01-11 23:35:25 +01:00
// ContainerInfo fetches JSON info for the container
func ( c Container ) ContainerInfo ( ) * types . ContainerJSON {
return c . containerInfo
}
2015-07-31 22:23:17 +00:00
// ID returns the Docker container ID.
2021-06-27 09:05:01 +02:00
func ( c Container ) ID ( ) wt . ContainerID {
return wt . ContainerID ( c . containerInfo . ID )
2015-07-22 22:52:22 +00:00
}
2019-05-12 09:29:52 +02:00
// 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
}
2021-06-24 00:36:33 +02:00
// IsRestarting returns a boolean flag indicating whether or not the current
// container is restarting. The status is determined by the value of the
// container's "State.Restarting" property.
func ( c Container ) IsRestarting ( ) bool {
return c . containerInfo . State . Restarting
}
2015-07-31 22:23:17 +00:00
// Name returns the Docker container name.
2015-07-14 20:51:12 +00:00
func ( c Container ) Name ( ) string {
return c . containerInfo . Name
}
2015-07-31 22:23:17 +00:00
// ImageID returns the ID of the Docker image that was used to start the
2021-06-27 09:05:01 +02:00
// container. May cause nil dereference if imageInfo is not set!
func ( c Container ) ImageID ( ) wt . ImageID {
return wt . ImageID ( c . imageInfo . ID )
}
// SafeImageID returns the ID of the Docker image that was used to start the container if available,
// otherwise returns an empty string
func ( c Container ) SafeImageID ( ) wt . ImageID {
if c . imageInfo == nil {
return ""
}
return wt . ImageID ( c . imageInfo . ID )
2015-07-31 18:24:27 +00:00
}
2015-07-31 22:23:17 +00:00
// 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.
2015-07-22 22:52:22 +00:00
func ( c Container ) ImageName ( ) string {
2015-08-12 20:49:45 +00:00
// Compatibility w/ Zodiac deployments
2019-07-27 01:37:16 +02:00
imageName , ok := c . getLabelValue ( zodiacLabel )
2015-08-12 20:49:45 +00:00
if ! ok {
imageName = c . containerInfo . Config . Image
}
2015-07-22 22:52:22 +00:00
if ! strings . Contains ( imageName , ":" ) {
imageName = fmt . Sprintf ( "%s:latest" , imageName )
}
return imageName
}
2017-10-17 06:53:30 +02:00
// Enabled returns the value of the container enabled label and if the label
// was set.
func ( c Container ) Enabled ( ) ( bool , bool ) {
2019-07-27 01:37:16 +02:00
rawBool , ok := c . getLabelValue ( enableLabel )
2017-10-17 06:53:30 +02:00
if ! ok {
return false , false
}
parsedBool , err := strconv . ParseBool ( rawBool )
if err != nil {
return false , false
}
return parsedBool , true
}
2023-09-08 16:53:05 +00:00
// IsMonitorOnly returns whether the container should only be monitored based on values of
// the monitor-only label, the monitor-only argument and the label-take-precedence argument.
func ( c Container ) IsMonitorOnly ( params wt . UpdateParams ) bool {
var containerMonitorOnlyLabel bool
MonitorOnlyLabelIsDefined := false
2020-10-03 22:00:02 +02:00
rawBool , ok := c . getLabelValue ( monitorOnlyLabel )
2023-09-08 16:53:05 +00:00
if ok {
parsedBool , err := strconv . ParseBool ( rawBool )
if err == nil {
MonitorOnlyLabelIsDefined = true
containerMonitorOnlyLabel = parsedBool
} else {
// Defaulting to false
containerMonitorOnlyLabel = false
}
} else {
// Defaulting to false
containerMonitorOnlyLabel = false
2020-10-03 22:00:02 +02:00
}
2023-09-08 16:53:05 +00:00
// in case MonitorOnly argument is true, the results change if the container monitor-only label is explicitly set to false if the label-take-precedence is true
if params . MonitorOnly {
if ( MonitorOnlyLabelIsDefined ) {
if params . LabelPrecedence {
return containerMonitorOnlyLabel
} else {
return true
}
} else {
return true
}
} else {
return containerMonitorOnlyLabel
2020-10-03 22:00:02 +02:00
}
}
2023-03-12 10:07:24 +01:00
// IsNoPull returns the value of the no-pull label. If the label is not set
// then false is returned.
func ( c Container ) IsNoPull ( ) bool {
rawBool , ok := c . getLabelValue ( noPullLabel )
if ! ok {
return false
}
parsedBool , err := strconv . ParseBool ( rawBool )
if err != nil {
return false
}
return parsedBool
}
2020-08-21 15:13:47 -03:00
// Scope returns the value of the scope UID label and if the label
// was set.
func ( c Container ) Scope ( ) ( string , bool ) {
rawString , ok := c . getLabelValue ( scope )
if ! ok {
return "" , false
}
return rawString , true
}
2015-07-31 22:23:17 +00:00
// Links returns a list containing the names of all the containers to which
// this container is linked.
2015-07-14 20:51:12 +00:00
func ( c Container ) Links ( ) [ ] string {
2015-07-21 21:40:22 +00:00
var links [ ] string
2015-07-14 20:51:12 +00:00
2020-04-24 14:41:04 +03:00
dependsOnLabelValue := c . getLabelValueOrEmpty ( dependsOnLabel )
if dependsOnLabelValue != "" {
2023-03-12 10:57:55 +01:00
for _ , link := range strings . Split ( dependsOnLabelValue , "," ) {
// Since the container names need to start with '/', let's prepend it if it's missing
if ! strings . HasPrefix ( link , "/" ) {
link = "/" + link
}
links = append ( links , link )
}
2020-04-24 14:41:04 +03:00
return links
}
2015-07-14 20:51:12 +00:00
if ( c . containerInfo != nil ) && ( c . containerInfo . HostConfig != nil ) {
for _ , link := range c . containerInfo . HostConfig . Links {
name := strings . Split ( link , ":" ) [ 0 ]
links = append ( links , name )
}
2023-08-08 18:32:44 +02:00
// If the container uses another container for networking, it can be considered an implicit link
// since the container would stop working if the network supplier were to be recreated
networkMode := c . containerInfo . HostConfig . NetworkMode
if networkMode . IsContainer ( ) {
links = append ( links , networkMode . ConnectedContainer ( ) )
}
2015-07-14 20:51:12 +00:00
}
return links
}
2019-07-27 01:37:16 +02:00
// ToRestart return whether the container should be restarted, either because
// is stale or linked to another stale container.
func ( c Container ) ToRestart ( ) bool {
2021-04-18 18:37:35 +02:00
return c . Stale || c . LinkedToRestarting
2019-07-27 01:37:16 +02:00
}
2015-07-31 22:23:17 +00:00
// 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.
2015-07-20 22:54:18 +00:00
func ( c Container ) IsWatchtower ( ) bool {
2019-04-20 16:44:41 +02:00
return ContainsWatchtowerLabel ( c . containerInfo . Config . Labels )
2015-07-20 22:54:18 +00:00
}
2019-11-25 21:11:12 +01:00
// PreUpdateTimeout checks whether a container has a specific timeout set
// for how long the pre-update command is allowed to run. This value is expressed
2020-03-28 19:48:04 +01:00
// either as an integer, in minutes, or as 0 which will allow the command/script
// to run indefinitely. Users should be cautious with the 0 option, as that
2019-11-25 21:11:12 +01:00
// could result in watchtower waiting forever.
func ( c Container ) PreUpdateTimeout ( ) int {
var minutes int
var err error
val := c . getLabelValueOrEmpty ( preUpdateTimeoutLabel )
minutes , err = strconv . Atoi ( val )
if err != nil || val == "" {
return 1
}
return minutes
}
2021-11-18 05:54:35 -08:00
// PostUpdateTimeout checks whether a container has a specific timeout set
// for how long the post-update command is allowed to run. This value is expressed
2022-01-22 17:40:37 +01:00
// either as an integer, in minutes, or as 0 which will allow the command/script
// to run indefinitely. Users should be cautious with the 0 option, as that
// could result in watchtower waiting forever.
func ( c Container ) PostUpdateTimeout ( ) int {
var minutes int
var err error
2021-11-18 05:54:35 -08:00
2022-01-22 17:40:37 +01:00
val := c . getLabelValueOrEmpty ( postUpdateTimeoutLabel )
2021-11-18 05:54:35 -08:00
2022-01-22 17:40:37 +01:00
minutes , err = strconv . Atoi ( val )
if err != nil || val == "" {
return 1
}
2021-11-18 05:54:35 -08:00
2022-01-22 17:40:37 +01:00
return minutes
}
2021-11-18 05:54:35 -08:00
2015-07-31 22:23:17 +00:00
// 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.
2015-07-22 22:52:22 +00:00
func ( c Container ) StopSignal ( ) string {
2019-07-27 01:37:16 +02:00
return c . getLabelValueOrEmpty ( signalLabel )
2015-07-22 22:52:22 +00:00
}
2023-04-12 17:36:01 +02:00
// GetCreateConfig returns the container's current Config converted into a format
// that can be re-submitted to the Docker create API.
//
2015-07-14 20:51:12 +00:00
// 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
2023-04-12 17:36:01 +02:00
// of the "docker run").
//
// In order to avoid unintentionally overriding the
2015-07-14 20:51:12 +00:00
// 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.
2023-04-12 17:36:01 +02:00
func ( c Container ) GetCreateConfig ( ) * dockercontainer . Config {
2015-07-14 20:51:12 +00:00
config := c . containerInfo . Config
2020-02-07 21:23:49 +01:00
hostConfig := c . containerInfo . HostConfig
2015-07-14 20:51:12 +00:00
imageConfig := c . imageInfo . Config
if config . WorkingDir == imageConfig . WorkingDir {
config . WorkingDir = ""
}
if config . User == imageConfig . User {
config . User = ""
}
2020-02-07 21:23:49 +01:00
if hostConfig . NetworkMode . IsContainer ( ) {
config . Hostname = ""
}
2019-07-21 19:58:19 +02:00
if util . SliceEqual ( config . Entrypoint , imageConfig . Entrypoint ) {
2015-07-20 22:54:18 +00:00
config . Entrypoint = nil
2019-12-18 16:40:02 -07:00
if util . SliceEqual ( config . Cmd , imageConfig . Cmd ) {
config . Cmd = nil
}
2015-07-14 20:51:12 +00:00
}
2019-07-21 19:58:19 +02:00
config . Env = util . SliceSubtract ( config . Env , imageConfig . Env )
2015-07-14 20:51:12 +00:00
2019-07-21 19:58:19 +02:00
config . Labels = util . StringMapSubtract ( config . Labels , imageConfig . Labels )
2015-07-14 20:51:12 +00:00
2019-07-21 19:58:19 +02:00
config . Volumes = util . StructMapSubtract ( config . Volumes , imageConfig . Volumes )
2015-07-14 20:51:12 +00:00
2016-11-07 19:21:47 +00:00
// subtract ports exposed in image from container
2018-03-02 22:37:50 +01:00
for k := range config . ExposedPorts {
2016-11-07 19:21:47 +00:00
if _ , ok := imageConfig . ExposedPorts [ k ] ; ok {
delete ( config . ExposedPorts , k )
}
}
2015-07-21 21:40:22 +00:00
for p := range c . containerInfo . HostConfig . PortBindings {
2015-07-14 20:51:12 +00:00
config . ExposedPorts [ p ] = struct { } { }
}
2015-08-12 20:49:45 +00:00
config . Image = c . ImageName ( )
2015-07-14 20:51:12 +00:00
return config
}
2023-04-12 17:36:01 +02:00
// GetCreateHostConfig returns the container's current HostConfig with any links
// re-written so that they can be re-submitted to the Docker create API.
func ( c Container ) GetCreateHostConfig ( ) * dockercontainer . HostConfig {
2015-07-14 20:51:12 +00:00
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
}
2020-08-18 20:55:35 +02:00
// HasImageInfo returns whether image information could be retrieved for the container
func ( c Container ) HasImageInfo ( ) bool {
return c . imageInfo != nil
}
2020-12-06 13:21:04 +01:00
// ImageInfo fetches the ImageInspect data of the current container
func ( c Container ) ImageInfo ( ) * types . ImageInspect {
return c . imageInfo
}
2021-04-24 18:29:05 +02:00
// VerifyConfiguration checks the container and image configurations for nil references to make sure
// that the container can be recreated once deleted
func ( c Container ) VerifyConfiguration ( ) error {
if c . imageInfo == nil {
return errorNoImageInfo
}
containerInfo := c . ContainerInfo ( )
if containerInfo == nil {
2021-11-12 06:21:34 -05:00
return errorNoContainerInfo
2021-04-24 18:29:05 +02:00
}
containerConfig := containerInfo . Config
if containerConfig == nil {
return errorInvalidConfig
}
hostConfig := containerInfo . HostConfig
if hostConfig == nil {
return errorInvalidConfig
}
2022-01-22 17:40:37 +01:00
// Instead of returning an error here, we just create an empty map
// This should allow for updating containers where the exposed ports are missing
2021-04-24 18:29:05 +02:00
if len ( hostConfig . PortBindings ) > 0 && containerConfig . ExposedPorts == nil {
2022-01-22 17:40:37 +01:00
containerConfig . ExposedPorts = make ( map [ nat . Port ] struct { } )
2021-04-24 18:29:05 +02:00
}
return nil
}