mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue