mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
fix: always use container interface (#1516)
This commit is contained in:
parent
25fdb40312
commit
dd1ec09668
12 changed files with 147 additions and 111 deletions
|
@ -25,15 +25,15 @@ const defaultStopSignal = "SIGTERM"
|
|||
// A Client is the interface through which watchtower interacts with the
|
||||
// Docker API.
|
||||
type Client interface {
|
||||
ListContainers(t.Filter) ([]Container, error)
|
||||
GetContainer(containerID t.ContainerID) (Container, error)
|
||||
StopContainer(Container, time.Duration) error
|
||||
StartContainer(Container) (t.ContainerID, error)
|
||||
RenameContainer(Container, string) error
|
||||
IsContainerStale(Container) (stale bool, latestImage t.ImageID, err error)
|
||||
ListContainers(t.Filter) ([]t.Container, error)
|
||||
GetContainer(containerID t.ContainerID) (t.Container, error)
|
||||
StopContainer(t.Container, time.Duration) error
|
||||
StartContainer(t.Container) (t.ContainerID, error)
|
||||
RenameContainer(t.Container, string) error
|
||||
IsContainerStale(t.Container) (stale bool, latestImage t.ImageID, err error)
|
||||
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
|
||||
RemoveImageByID(t.ImageID) error
|
||||
WarnOnHeadPullFailed(container Container) bool
|
||||
WarnOnHeadPullFailed(container t.Container) bool
|
||||
}
|
||||
|
||||
// NewClient returns a new Client instance which can be used to interact with
|
||||
|
@ -82,7 +82,7 @@ type dockerClient struct {
|
|||
ClientOptions
|
||||
}
|
||||
|
||||
func (client dockerClient) WarnOnHeadPullFailed(container Container) bool {
|
||||
func (client dockerClient) WarnOnHeadPullFailed(container t.Container) bool {
|
||||
if client.WarnOnHeadFailed == WarnAlways {
|
||||
return true
|
||||
}
|
||||
|
@ -93,8 +93,8 @@ func (client dockerClient) WarnOnHeadPullFailed(container Container) bool {
|
|||
return registry.WarnOnAPIConsumption(container)
|
||||
}
|
||||
|
||||
func (client dockerClient) ListContainers(fn t.Filter) ([]Container, error) {
|
||||
cs := []Container{}
|
||||
func (client dockerClient) ListContainers(fn t.Filter) ([]t.Container, error) {
|
||||
cs := []t.Container{}
|
||||
bg := context.Background()
|
||||
|
||||
if client.IncludeStopped && client.IncludeRestarting {
|
||||
|
@ -149,24 +149,24 @@ func (client dockerClient) createListFilter() filters.Args {
|
|||
return filterArgs
|
||||
}
|
||||
|
||||
func (client dockerClient) GetContainer(containerID t.ContainerID) (Container, error) {
|
||||
func (client dockerClient) GetContainer(containerID t.ContainerID) (t.Container, error) {
|
||||
bg := context.Background()
|
||||
|
||||
containerInfo, err := client.api.ContainerInspect(bg, string(containerID))
|
||||
if err != nil {
|
||||
return Container{}, err
|
||||
return &Container{}, err
|
||||
}
|
||||
|
||||
imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to retrieve container image info: %v", err)
|
||||
return Container{containerInfo: &containerInfo, imageInfo: nil}, nil
|
||||
return &Container{containerInfo: &containerInfo, imageInfo: nil}, nil
|
||||
}
|
||||
|
||||
return Container{containerInfo: &containerInfo, imageInfo: &imageInfo}, nil
|
||||
return &Container{containerInfo: &containerInfo, imageInfo: &imageInfo}, nil
|
||||
}
|
||||
|
||||
func (client dockerClient) StopContainer(c Container, timeout time.Duration) error {
|
||||
func (client dockerClient) StopContainer(c t.Container, timeout time.Duration) error {
|
||||
bg := context.Background()
|
||||
signal := c.StopSignal()
|
||||
if signal == "" {
|
||||
|
@ -186,7 +186,7 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
|
|||
// TODO: This should probably be checked.
|
||||
_ = client.waitForStopOrTimeout(c, timeout)
|
||||
|
||||
if c.containerInfo.HostConfig.AutoRemove {
|
||||
if c.ContainerInfo().HostConfig.AutoRemove {
|
||||
log.Debugf("AutoRemove container %s, skipping ContainerRemove call.", shortID)
|
||||
} else {
|
||||
log.Debugf("Removing container %s", shortID)
|
||||
|
@ -208,11 +208,11 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (client dockerClient) StartContainer(c Container) (t.ContainerID, error) {
|
||||
func (client dockerClient) StartContainer(c t.Container) (t.ContainerID, error) {
|
||||
bg := context.Background()
|
||||
config := c.runtimeConfig()
|
||||
hostConfig := c.hostConfig()
|
||||
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.containerInfo.NetworkSettings.Networks}
|
||||
config := c.GetCreateConfig()
|
||||
hostConfig := c.GetCreateHostConfig()
|
||||
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.ContainerInfo().NetworkSettings.Networks}
|
||||
// simpleNetworkConfig is a networkConfig with only 1 network.
|
||||
// see: https://github.com/docker/docker/issues/29265
|
||||
simpleNetworkConfig := func() *network.NetworkingConfig {
|
||||
|
@ -260,7 +260,7 @@ func (client dockerClient) StartContainer(c Container) (t.ContainerID, error) {
|
|||
|
||||
}
|
||||
|
||||
func (client dockerClient) doStartContainer(bg context.Context, c Container, creation container.CreateResponse) error {
|
||||
func (client dockerClient) doStartContainer(bg context.Context, c t.Container, creation container.CreateResponse) error {
|
||||
name := c.Name()
|
||||
|
||||
log.Debugf("Starting container %s (%s)", name, t.ContainerID(creation.ID).ShortID())
|
||||
|
@ -271,13 +271,13 @@ func (client dockerClient) doStartContainer(bg context.Context, c Container, cre
|
|||
return nil
|
||||
}
|
||||
|
||||
func (client dockerClient) RenameContainer(c Container, newName string) error {
|
||||
func (client dockerClient) RenameContainer(c t.Container, newName string) error {
|
||||
bg := context.Background()
|
||||
log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID().ShortID(), newName)
|
||||
return client.api.ContainerRename(bg, string(c.ID()), newName)
|
||||
}
|
||||
|
||||
func (client dockerClient) IsContainerStale(container Container) (stale bool, latestImage t.ImageID, err error) {
|
||||
func (client dockerClient) IsContainerStale(container t.Container) (stale bool, latestImage t.ImageID, err error) {
|
||||
ctx := context.Background()
|
||||
|
||||
if !client.PullImages || container.IsNoPull() {
|
||||
|
@ -289,8 +289,8 @@ func (client dockerClient) IsContainerStale(container Container) (stale bool, la
|
|||
return client.HasNewImage(ctx, container)
|
||||
}
|
||||
|
||||
func (client dockerClient) HasNewImage(ctx context.Context, container Container) (hasNew bool, latestImage t.ImageID, err error) {
|
||||
currentImageID := t.ImageID(container.containerInfo.ContainerJSONBase.Image)
|
||||
func (client dockerClient) HasNewImage(ctx context.Context, container t.Container) (hasNew bool, latestImage t.ImageID, err error) {
|
||||
currentImageID := t.ImageID(container.ContainerInfo().ContainerJSONBase.Image)
|
||||
imageName := container.ImageName()
|
||||
|
||||
newImageInfo, _, err := client.api.ImageInspectWithRaw(ctx, imageName)
|
||||
|
@ -310,7 +310,7 @@ func (client dockerClient) HasNewImage(ctx context.Context, container Container)
|
|||
|
||||
// PullImage pulls the latest image for the supplied container, optionally skipping if it's digest can be confirmed
|
||||
// to match the one that the registry reports via a HEAD request
|
||||
func (client dockerClient) PullImage(ctx context.Context, container Container) error {
|
||||
func (client dockerClient) PullImage(ctx context.Context, container t.Container) error {
|
||||
containerName := container.Name()
|
||||
imageName := container.ImageName()
|
||||
|
||||
|
@ -478,7 +478,7 @@ func (client dockerClient) waitForExecOrTimeout(bg context.Context, ID string, e
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (client dockerClient) waitForStopOrTimeout(c Container, waitTime time.Duration) error {
|
||||
func (client dockerClient) waitForStopOrTimeout(c t.Container, waitTime time.Duration) error {
|
||||
bg := context.Background()
|
||||
timeout := time.After(waitTime)
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ var _ = Describe("the client", func() {
|
|||
mockServer.Close()
|
||||
})
|
||||
Describe("WarnOnHeadPullFailed", func() {
|
||||
containerUnknown := *MockContainer(WithImageName("unknown.repo/prefix/imagename:latest"))
|
||||
containerKnown := *MockContainer(WithImageName("docker.io/prefix/imagename:latest"))
|
||||
containerUnknown := MockContainer(WithImageName("unknown.repo/prefix/imagename:latest"))
|
||||
containerKnown := MockContainer(WithImageName("docker.io/prefix/imagename:latest"))
|
||||
|
||||
When(`warn on head failure is set to "always"`, func() {
|
||||
c := dockerClient{ClientOptions: ClientOptions{WarnOnHeadFailed: WarnAlways}}
|
||||
|
@ -66,7 +66,7 @@ var _ = Describe("the client", func() {
|
|||
When("the image consist of a pinned hash", func() {
|
||||
It("should gracefully fail with a useful message", func() {
|
||||
c := dockerClient{}
|
||||
pinnedContainer := *MockContainer(WithImageName("sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"))
|
||||
pinnedContainer := MockContainer(WithImageName("sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"))
|
||||
c.PullImage(context.Background(), pinnedContainer)
|
||||
})
|
||||
})
|
||||
|
@ -74,8 +74,8 @@ var _ = Describe("the client", func() {
|
|||
When("removing a running container", func() {
|
||||
When("the container still exist after stopping", func() {
|
||||
It("should attempt to remove the container", func() {
|
||||
container := *MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||
containerStopped := *MockContainer(WithContainerState(types.ContainerState{Running: false}))
|
||||
container := MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||
containerStopped := MockContainer(WithContainerState(types.ContainerState{Running: false}))
|
||||
|
||||
cid := container.ContainerInfo().ID
|
||||
mockServer.AppendHandlers(
|
||||
|
@ -90,7 +90,7 @@ var _ = Describe("the client", func() {
|
|||
})
|
||||
When("the container does not exist after stopping", func() {
|
||||
It("should not cause an error", func() {
|
||||
container := *MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||
container := MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||
|
||||
cid := container.ContainerInfo().ID
|
||||
mockServer.AppendHandlers(
|
||||
|
@ -261,18 +261,18 @@ func withContainerImageName(matcher gt.GomegaMatcher) gt.GomegaMatcher {
|
|||
return WithTransform(containerImageName, matcher)
|
||||
}
|
||||
|
||||
func containerImageName(container Container) string {
|
||||
func containerImageName(container t.Container) string {
|
||||
return container.ImageName()
|
||||
}
|
||||
|
||||
func havingRestartingState(expected bool) gt.GomegaMatcher {
|
||||
return WithTransform(func(container Container) bool {
|
||||
return container.containerInfo.State.Restarting
|
||||
return WithTransform(func(container t.Container) bool {
|
||||
return container.ContainerInfo().State.Restarting
|
||||
}, Equal(expected))
|
||||
}
|
||||
|
||||
func havingRunningState(expected bool) gt.GomegaMatcher {
|
||||
return WithTransform(func(container Container) bool {
|
||||
return container.containerInfo.State.Running
|
||||
return WithTransform(func(container t.Container) bool {
|
||||
return container.ContainerInfo().State.Running
|
||||
}, Equal(expected))
|
||||
}
|
||||
|
|
|
@ -32,6 +32,26 @@ type Container struct {
|
|||
imageInfo *types.ImageInspect
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ContainerInfo fetches JSON info for the container
|
||||
func (c Container) ContainerInfo() *types.ContainerJSON {
|
||||
return c.containerInfo
|
||||
|
@ -240,18 +260,23 @@ func (c Container) StopSignal() string {
|
|||
return c.getLabelValueOrEmpty(signalLabel)
|
||||
}
|
||||
|
||||
// GetCreateConfig returns the container's current Config converted into a format
|
||||
// that can be re-submitted to the Docker create API.
|
||||
//
|
||||
// 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
|
||||
// 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 {
|
||||
func (c Container) GetCreateConfig() *dockercontainer.Config {
|
||||
config := c.containerInfo.Config
|
||||
hostConfig := c.containerInfo.HostConfig
|
||||
imageConfig := c.imageInfo.Config
|
||||
|
@ -295,9 +320,9 @@ func (c Container) runtimeConfig() *dockercontainer.Config {
|
|||
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 {
|
||||
// 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 {
|
||||
hostConfig := c.containerInfo.HostConfig
|
||||
|
||||
for i, link := range hostConfig.Links {
|
||||
|
|
|
@ -29,7 +29,7 @@ func ExecutePostChecks(client container.Client, params types.UpdateParams) {
|
|||
}
|
||||
|
||||
// ExecutePreCheckCommand tries to run the pre-check lifecycle hook for a single container.
|
||||
func ExecutePreCheckCommand(client container.Client, container container.Container) {
|
||||
func ExecutePreCheckCommand(client container.Client, container types.Container) {
|
||||
clog := log.WithField("container", container.Name())
|
||||
command := container.GetLifecyclePreCheckCommand()
|
||||
if len(command) == 0 {
|
||||
|
@ -45,7 +45,7 @@ func ExecutePreCheckCommand(client container.Client, container container.Contain
|
|||
}
|
||||
|
||||
// ExecutePostCheckCommand tries to run the post-check lifecycle hook for a single container.
|
||||
func ExecutePostCheckCommand(client container.Client, container container.Container) {
|
||||
func ExecutePostCheckCommand(client container.Client, container types.Container) {
|
||||
clog := log.WithField("container", container.Name())
|
||||
command := container.GetLifecyclePostCheckCommand()
|
||||
if len(command) == 0 {
|
||||
|
@ -61,7 +61,7 @@ func ExecutePostCheckCommand(client container.Client, container container.Contai
|
|||
}
|
||||
|
||||
// ExecutePreUpdateCommand tries to run the pre-update lifecycle hook for a single container.
|
||||
func ExecutePreUpdateCommand(client container.Client, container container.Container) (SkipUpdate bool, err error) {
|
||||
func ExecutePreUpdateCommand(client container.Client, container types.Container) (SkipUpdate bool, err error) {
|
||||
timeout := container.PreUpdateTimeout()
|
||||
command := container.GetLifecyclePreUpdateCommand()
|
||||
clog := log.WithField("container", container.Name())
|
||||
|
|
|
@ -2,13 +2,14 @@ package sorter
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containrrr/watchtower/pkg/container"
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
)
|
||||
|
||||
// ByCreated allows a list of Container structs to be sorted by the container's
|
||||
// created date.
|
||||
type ByCreated []container.Container
|
||||
type ByCreated []types.Container
|
||||
|
||||
func (c ByCreated) Len() int { return len(c) }
|
||||
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
@ -34,18 +35,18 @@ func (c ByCreated) Less(i, j int) bool {
|
|||
// the front of the list while containers with links will be sorted after all
|
||||
// of their dependencies. This sort order ensures that linked containers can
|
||||
// be started in the correct order.
|
||||
func SortByDependencies(containers []container.Container) ([]container.Container, error) {
|
||||
func SortByDependencies(containers []types.Container) ([]types.Container, error) {
|
||||
sorter := dependencySorter{}
|
||||
return sorter.Sort(containers)
|
||||
}
|
||||
|
||||
type dependencySorter struct {
|
||||
unvisited []container.Container
|
||||
unvisited []types.Container
|
||||
marked map[string]bool
|
||||
sorted []container.Container
|
||||
sorted []types.Container
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) Sort(containers []container.Container) ([]container.Container, error) {
|
||||
func (ds *dependencySorter) Sort(containers []types.Container) ([]types.Container, error) {
|
||||
ds.unvisited = containers
|
||||
ds.marked = map[string]bool{}
|
||||
|
||||
|
@ -58,7 +59,7 @@ func (ds *dependencySorter) Sort(containers []container.Container) ([]container.
|
|||
return ds.sorted, nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) visit(c container.Container) error {
|
||||
func (ds *dependencySorter) visit(c types.Container) error {
|
||||
|
||||
if _, ok := ds.marked[c.Name()]; ok {
|
||||
return fmt.Errorf("circular reference to %s", c.Name())
|
||||
|
@ -84,7 +85,7 @@ func (ds *dependencySorter) visit(c container.Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) findUnvisited(name string) *container.Container {
|
||||
func (ds *dependencySorter) findUnvisited(name string) *types.Container {
|
||||
for _, c := range ds.unvisited {
|
||||
if c.Name() == name {
|
||||
return &c
|
||||
|
@ -94,7 +95,7 @@ func (ds *dependencySorter) findUnvisited(name string) *container.Container {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ds *dependencySorter) removeUnvisited(c container.Container) {
|
||||
func (ds *dependencySorter) removeUnvisited(c types.Container) {
|
||||
var idx int
|
||||
for i := range ds.unvisited {
|
||||
if ds.unvisited[i].Name() == c.Name() {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
dc "github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
// ImageID is a hash string representing a container image
|
||||
|
@ -62,4 +64,15 @@ type Container interface {
|
|||
GetLifecyclePostCheckCommand() string
|
||||
GetLifecyclePreUpdateCommand() string
|
||||
GetLifecyclePostUpdateCommand() string
|
||||
VerifyConfiguration() error
|
||||
SetStale(bool)
|
||||
IsStale() bool
|
||||
IsNoPull() bool
|
||||
SetLinkedToRestarting(bool)
|
||||
IsLinkedToRestarting() bool
|
||||
PreUpdateTimeout() int
|
||||
PostUpdateTimeout() int
|
||||
IsRestarting() bool
|
||||
GetCreateConfig() *dc.Config
|
||||
GetCreateHostConfig() *dc.HostConfig
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue