Skip to content

Commit e9c83af

Browse files
authored
fix: correctly handle non-stale restarts (#1220)
1 parent d12ce7c commit e9c83af

File tree

4 files changed

+166
-93
lines changed

4 files changed

+166
-93
lines changed

internal/actions/mocks/client.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package mocks
33
import (
44
"errors"
55
"fmt"
6-
"github.com/containrrr/watchtower/pkg/container"
76
"time"
87

8+
"github.com/containrrr/watchtower/pkg/container"
9+
910
t "github.com/containrrr/watchtower/pkg/types"
1011
)
1112

@@ -21,6 +22,7 @@ type TestData struct {
2122
TriedToRemoveImageCount int
2223
NameOfContainerToKeep string
2324
Containers []container.Container
25+
Staleness map[string]bool
2426
}
2527

2628
// TriedToRemoveImage is a test helper function to check whether RemoveImageByID has been called
@@ -85,9 +87,13 @@ func (client MockClient) ExecuteCommand(_ t.ContainerID, command string, _ int)
8587
}
8688
}
8789

88-
// IsContainerStale is always true for the mock client
89-
func (client MockClient) IsContainerStale(_ container.Container) (bool, t.ImageID, error) {
90-
return true, "", nil
90+
// IsContainerStale is true if not explicitly stated in TestData for the mock client
91+
func (client MockClient) IsContainerStale(cont container.Container) (bool, t.ImageID, error) {
92+
stale, found := client.TestData.Staleness[cont.Name()]
93+
if !found {
94+
stale = true
95+
}
96+
return stale, "", nil
9197
}
9298

9399
// WarnOnHeadPullFailed is always true for the mock client

internal/actions/mocks/container.go

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package mocks
22

33
import (
44
"fmt"
5+
"strconv"
6+
"strings"
7+
"time"
8+
59
"github.com/containrrr/watchtower/pkg/container"
610
wt "github.com/containrrr/watchtower/pkg/types"
711
"github.com/docker/docker/api/types"
812
dockerContainer "github.com/docker/docker/api/types/container"
913
"github.com/docker/go-connections/nat"
10-
"strconv"
11-
"strings"
12-
"time"
1314
)
1415

1516
// CreateMockContainer creates a container substitute valid for testing
@@ -32,15 +33,20 @@ func CreateMockContainer(id string, name string, image string, created time.Time
3233
}
3334
return *container.NewContainer(
3435
&content,
35-
&types.ImageInspect{
36-
ID: image,
37-
RepoDigests: []string{
38-
image,
39-
},
40-
},
36+
CreateMockImageInfo(image),
4137
)
4238
}
4339

40+
// CreateMockImageInfo returns a mock image info struct based on the passed image
41+
func CreateMockImageInfo(image string) *types.ImageInspect {
42+
return &types.ImageInspect{
43+
ID: image,
44+
RepoDigests: []string{
45+
image,
46+
},
47+
}
48+
}
49+
4450
// CreateMockContainerWithImageInfo should only be used for testing
4551
func CreateMockContainerWithImageInfo(id string, name string, image string, created time.Time, imageInfo types.ImageInspect) container.Container {
4652
return CreateMockContainerWithImageInfoP(id, name, image, created, &imageInfo)
@@ -93,9 +99,7 @@ func CreateMockContainerWithConfig(id string, name string, image string, running
9399
}
94100
return *container.NewContainer(
95101
&content,
96-
&types.ImageInspect{
97-
ID: image,
98-
},
102+
CreateMockImageInfo(image),
99103
)
100104
}
101105

@@ -114,3 +118,26 @@ func CreateContainerForProgress(index int, idPrefix int, nameFormat string) (con
114118
c := CreateMockContainerWithConfig(contID, contName, oldImgID, true, false, time.Now(), config)
115119
return c, wt.ImageID(newImgID)
116120
}
121+
122+
// CreateMockContainerWithLinks should only be used for testing
123+
func CreateMockContainerWithLinks(id string, name string, image string, created time.Time, links []string, imageInfo *types.ImageInspect) container.Container {
124+
content := types.ContainerJSON{
125+
ContainerJSONBase: &types.ContainerJSONBase{
126+
ID: id,
127+
Image: image,
128+
Name: name,
129+
Created: created.String(),
130+
HostConfig: &dockerContainer.HostConfig{
131+
Links: links,
132+
},
133+
},
134+
Config: &dockerContainer.Config{
135+
Image: image,
136+
Labels: make(map[string]string),
137+
},
138+
}
139+
return *container.NewContainer(
140+
&content,
141+
imageInfo,
142+
)
143+
}

internal/actions/update.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package actions
22

33
import (
44
"errors"
5+
"strings"
6+
57
"github.com/containrrr/watchtower/internal/util"
68
"github.com/containrrr/watchtower/pkg/container"
79
"github.com/containrrr/watchtower/pkg/lifecycle"
810
"github.com/containrrr/watchtower/pkg/session"
911
"github.com/containrrr/watchtower/pkg/sorter"
1012
"github.com/containrrr/watchtower/pkg/types"
1113
log "github.com/sirupsen/logrus"
12-
"strings"
1314
)
1415

1516
// Update looks at the running Docker containers to see if any of the images
@@ -108,8 +109,10 @@ func performRollingRestart(containers []container.Container, client container.Cl
108109
} else {
109110
if err := restartStaleContainer(containers[i], client, params); err != nil {
110111
failed[containers[i].ID()] = err
112+
} else if containers[i].Stale {
113+
// Only add (previously) stale containers' images to cleanup
114+
cleanupImageIDs[containers[i].ImageID()] = true
111115
}
112-
cleanupImageIDs[containers[i].ImageID()] = true
113116
}
114117
}
115118
}
@@ -127,7 +130,8 @@ func stopContainersInReversedOrder(containers []container.Container, client cont
127130
if err := stopStaleContainer(containers[i], client, params); err != nil {
128131
failed[containers[i].ID()] = err
129132
} else {
130-
stopped[containers[i].ImageID()] = true
133+
// NOTE: If a container is restarted due to a dependency this might be empty
134+
stopped[containers[i].SafeImageID()] = true
131135
}
132136

133137
}
@@ -143,6 +147,14 @@ func stopStaleContainer(container container.Container, client container.Client,
143147
if !container.ToRestart() {
144148
return nil
145149
}
150+
151+
// Perform an additional check here to prevent us from stopping a linked container we cannot restart
152+
if container.LinkedToRestarting {
153+
if err := container.VerifyConfiguration(); err != nil {
154+
return err
155+
}
156+
}
157+
146158
if params.LifecycleHooks {
147159
skipUpdate, err := lifecycle.ExecutePreUpdateCommand(client, container)
148160
if err != nil {
@@ -171,11 +183,13 @@ func restartContainersInSortedOrder(containers []container.Container, client con
171183
if !c.ToRestart() {
172184
continue
173185
}
174-
if stoppedImages[c.ImageID()] {
186+
if stoppedImages[c.SafeImageID()] {
175187
if err := restartStaleContainer(c, client, params); err != nil {
176188
failed[c.ID()] = err
189+
} else if c.Stale {
190+
// Only add (previously) stale containers' images to cleanup
191+
cleanupImageIDs[c.ImageID()] = true
177192
}
178-
cleanupImageIDs[c.ImageID()] = true
179193
}
180194
}
181195

@@ -188,6 +202,9 @@ func restartContainersInSortedOrder(containers []container.Container, client con
188202

189203
func cleanupImages(client container.Client, imageIDs map[types.ImageID]bool) {
190204
for imageID := range imageIDs {
205+
if imageID == "" {
206+
continue
207+
}
191208
if err := client.RemoveImageByID(imageID); err != nil {
192209
log.Error(err)
193210
}

0 commit comments

Comments
 (0)