mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
feat: ignore removal error due to non-existing containers (#1481)
Co-authored-by: nils måsén <nils@piksel.se> Fixes https://github.com/containrrr/watchtower/issues/1480
This commit is contained in:
parent
a4d00bfd75
commit
3190ce2df1
3 changed files with 115 additions and 12 deletions
|
@ -192,6 +192,10 @@ func (client dockerClient) StopContainer(c Container, timeout time.Duration) err
|
||||||
log.Debugf("Removing container %s", shortID)
|
log.Debugf("Removing container %s", shortID)
|
||||||
|
|
||||||
if err := client.api.ContainerRemove(bg, idStr, types.ContainerRemoveOptions{Force: true, RemoveVolumes: client.RemoveVolumes}); err != nil {
|
if err := client.api.ContainerRemove(bg, idStr, types.ContainerRemoveOptions{Force: true, RemoveVolumes: client.RemoveVolumes}); err != nil {
|
||||||
|
if sdkClient.IsErrNotFound(err) {
|
||||||
|
log.Debugf("Container %s not found, skipping removal.", shortID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/container/mocks"
|
"github.com/containrrr/watchtower/pkg/container/mocks"
|
||||||
"github.com/containrrr/watchtower/pkg/filters"
|
"github.com/containrrr/watchtower/pkg/filters"
|
||||||
t "github.com/containrrr/watchtower/pkg/types"
|
t "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
@ -69,6 +71,38 @@ 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}))
|
||||||
|
|
||||||
|
cid := container.ContainerInfo().ID
|
||||||
|
mockServer.AppendHandlers(
|
||||||
|
mocks.KillContainerHandler(cid, mocks.Found),
|
||||||
|
mocks.GetContainerHandler(cid, containerStopped.ContainerInfo()),
|
||||||
|
mocks.RemoveContainerHandler(cid, mocks.Found),
|
||||||
|
mocks.GetContainerHandler(cid, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(dockerClient{api: docker}.StopContainer(container, time.Minute)).To(Succeed())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
When("the container does not exist after stopping", func() {
|
||||||
|
It("should not cause an error", func() {
|
||||||
|
container := *MockContainer(WithContainerState(types.ContainerState{Running: true}))
|
||||||
|
|
||||||
|
cid := container.ContainerInfo().ID
|
||||||
|
mockServer.AppendHandlers(
|
||||||
|
mocks.KillContainerHandler(cid, mocks.Found),
|
||||||
|
mocks.GetContainerHandler(cid, nil),
|
||||||
|
mocks.RemoveContainerHandler(cid, mocks.Missing),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(dockerClient{api: docker}.StopContainer(container, time.Minute)).To(Succeed())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
When("listing containers", func() {
|
When("listing containers", func() {
|
||||||
When("no filter is provided", func() {
|
When("no filter is provided", func() {
|
||||||
It("should return all available containers", func() {
|
It("should return all available containers", func() {
|
||||||
|
|
|
@ -3,14 +3,15 @@ package mocks
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
O "github.com/onsi/gomega"
|
|
||||||
"github.com/onsi/gomega/ghttp"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
O "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/ghttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getMockJSONFile(relPath string) ([]byte, error) {
|
func getMockJSONFile(relPath string) ([]byte, error) {
|
||||||
|
@ -42,14 +43,14 @@ func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...http.
|
||||||
func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc {
|
func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc {
|
||||||
handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2)
|
handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2)
|
||||||
for _, file := range containerFiles {
|
for _, file := range containerFiles {
|
||||||
handlers = append(handlers, getContainerHandler(file))
|
handlers = append(handlers, getContainerFileHandler(file))
|
||||||
|
|
||||||
// Also append the image request since that will be called for every container
|
// Also append the image request since that will be called for every container
|
||||||
if file == "running" {
|
if file == "running" {
|
||||||
// The "running" container is the only one using image02
|
// The "running" container is the only one using image02
|
||||||
handlers = append(handlers, getImageHandler(1))
|
handlers = append(handlers, getImageFileHandler(1))
|
||||||
} else {
|
} else {
|
||||||
handlers = append(handlers, getImageHandler(0))
|
handlers = append(handlers, getImageFileHandler(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handlers
|
return handlers
|
||||||
|
@ -75,15 +76,36 @@ var imageIds = []string{
|
||||||
"sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
|
"sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerHandler(file string) http.HandlerFunc {
|
func getContainerFileHandler(file string) http.HandlerFunc {
|
||||||
id, ok := containerFileIds[file]
|
id, ok := containerFileIds[file]
|
||||||
failTestUnless(ok)
|
failTestUnless(ok)
|
||||||
return ghttp.CombineHandlers(
|
return getContainerHandler(
|
||||||
ghttp.VerifyRequest("GET", O.HaveSuffix("/containers/%v/json", id)),
|
id,
|
||||||
RespondWithJSONFile(fmt.Sprintf("./mocks/data/container_%v.json", file), http.StatusOK),
|
RespondWithJSONFile(fmt.Sprintf("./mocks/data/container_%v.json", file), http.StatusOK),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContainerHandler(containerId string, responseHandler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return ghttp.CombineHandlers(
|
||||||
|
ghttp.VerifyRequest("GET", O.HaveSuffix("/containers/%v/json", containerId)),
|
||||||
|
responseHandler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContainerHandler mocks the GET containers/{id}/json endpoint
|
||||||
|
func GetContainerHandler(containerID string, containerInfo *types.ContainerJSON) http.HandlerFunc {
|
||||||
|
responseHandler := containerNotFoundResponse(containerID)
|
||||||
|
if containerInfo != nil {
|
||||||
|
responseHandler = ghttp.RespondWithJSONEncoded(http.StatusOK, containerInfo)
|
||||||
|
}
|
||||||
|
return getContainerHandler(containerID, responseHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImageHandler mocks the GET images/{id}/json endpoint
|
||||||
|
func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc {
|
||||||
|
return getImageHandler(imageInfo.ID, ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo))
|
||||||
|
}
|
||||||
|
|
||||||
// ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses
|
// ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses
|
||||||
func ListContainersHandler(statuses ...string) http.HandlerFunc {
|
func ListContainersHandler(statuses ...string) http.HandlerFunc {
|
||||||
filterArgs := createFilterArgs(statuses)
|
filterArgs := createFilterArgs(statuses)
|
||||||
|
@ -116,9 +138,15 @@ func respondWithFilteredContainers(filters filters.Args) http.HandlerFunc {
|
||||||
return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers)
|
return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageHandler(index int) http.HandlerFunc {
|
func getImageHandler(imageId string, responseHandler http.HandlerFunc) http.HandlerFunc {
|
||||||
return ghttp.CombineHandlers(
|
return ghttp.CombineHandlers(
|
||||||
ghttp.VerifyRequest("GET", O.HaveSuffix("/images/%v/json", imageIds[index])),
|
ghttp.VerifyRequest("GET", O.HaveSuffix("/images/%s/json", imageId)),
|
||||||
|
responseHandler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImageFileHandler(index int) http.HandlerFunc {
|
||||||
|
return getImageHandler(imageIds[index],
|
||||||
RespondWithJSONFile(fmt.Sprintf("./mocks/data/image%02d.json", index+1), http.StatusOK),
|
RespondWithJSONFile(fmt.Sprintf("./mocks/data/image%02d.json", index+1), http.StatusOK),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -126,3 +154,40 @@ func getImageHandler(index int) http.HandlerFunc {
|
||||||
func failTestUnless(ok bool) {
|
func failTestUnless(ok bool) {
|
||||||
O.ExpectWithOffset(2, ok).To(O.BeTrue(), "test setup failed")
|
O.ExpectWithOffset(2, ok).To(O.BeTrue(), "test setup failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KillContainerHandler mocks the POST containers/{id}/kill endpoint
|
||||||
|
func KillContainerHandler(containerID string, found FoundStatus) http.HandlerFunc {
|
||||||
|
responseHandler := noContentStatusResponse
|
||||||
|
if !found {
|
||||||
|
responseHandler = containerNotFoundResponse(containerID)
|
||||||
|
}
|
||||||
|
return ghttp.CombineHandlers(
|
||||||
|
ghttp.VerifyRequest("POST", O.HaveSuffix("containers/%s/kill", containerID)),
|
||||||
|
responseHandler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveContainerHandler mocks the DELETE containers/{id} endpoint
|
||||||
|
func RemoveContainerHandler(containerID string, found FoundStatus) http.HandlerFunc {
|
||||||
|
responseHandler := noContentStatusResponse
|
||||||
|
if !found {
|
||||||
|
responseHandler = containerNotFoundResponse(containerID)
|
||||||
|
}
|
||||||
|
return ghttp.CombineHandlers(
|
||||||
|
ghttp.VerifyRequest("DELETE", O.HaveSuffix("containers/%s", containerID)),
|
||||||
|
responseHandler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerNotFoundResponse(containerID string) http.HandlerFunc {
|
||||||
|
return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + containerID})
|
||||||
|
}
|
||||||
|
|
||||||
|
var noContentStatusResponse = ghttp.RespondWith(http.StatusNoContent, nil)
|
||||||
|
|
||||||
|
type FoundStatus bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
Found FoundStatus = true
|
||||||
|
Missing FoundStatus = false
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue