From 26fba69169e45fec10e6e11358d323c749f227f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Mon, 1 Nov 2021 19:26:41 +0100 Subject: [PATCH] test: refactor client tests should not be explicitly telling what they are testing and the requirements fixed the test data so that it doesn't contain discrepancies fully reset the mock client (no shared state) and only support the calls that is expected --- pkg/container/client_test.go | 245 ++++++++++++------ pkg/container/mocks/ApiServer.go | 176 ++++++++----- .../mocks/data/container_stopped.json | 2 +- .../mocks/data/container_watchtower.json | 205 +++++++++++++++ pkg/container/mocks/data/containers.json | 52 +++- 5 files changed, 527 insertions(+), 153 deletions(-) create mode 100644 pkg/container/mocks/data/container_watchtower.json diff --git a/pkg/container/client_test.go b/pkg/container/client_test.go index 01e4c47..d9bf86f 100644 --- a/pkg/container/client_test.go +++ b/pkg/container/client_test.go @@ -3,28 +3,33 @@ package container import ( "github.com/containrrr/watchtower/pkg/container/mocks" "github.com/containrrr/watchtower/pkg/filters" + t "github.com/containrrr/watchtower/pkg/types" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/backend" cli "github.com/docker/docker/client" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/ghttp" "github.com/sirupsen/logrus" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/types" + + "net/http" ) var _ = Describe("the client", func() { var docker *cli.Client - var client Client + var mockServer *ghttp.Server BeforeSuite(func() { - server := mocks.NewMockAPIServer() + mockServer = ghttp.NewServer() docker, _ = cli.NewClientWithOpts( - cli.WithHost(server.URL), - cli.WithHTTPClient(server.Client())) - client = dockerClient{ - api: docker, - pullImages: false, - } + cli.WithHost(mockServer.URL()), + cli.WithHTTPClient(mockServer.HTTPTestServer.Client())) }) - It("should return a client for the api", func() { - Expect(client).NotTo(BeNil()) + AfterEach(func() { + mockServer.Reset() }) Describe("WarnOnHeadPullFailed", func() { containerUnknown := *mockContainerWithImageName("unknown.repo/prefix/imagename:latest") @@ -54,90 +59,152 @@ var _ = Describe("the client", func() { }) }) }) - - When("listing containers without any filter", func() { - It("should return all available containers", func() { - containers, err := client.ListContainers(filters.NoFilter) - Expect(err).NotTo(HaveOccurred()) - Expect(len(containers) == 2).To(BeTrue()) + When("listing containers", func() { + When("no filter is provided", func() { + It("should return all available containers", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + client := dockerClient{ + api: docker, + pullImages: false, + } + containers, err := client.ListContainers(filters.NoFilter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).To(HaveLen(2)) + }) }) - }) - When("listing containers with a filter matching nothing", func() { - It("should return an empty array", func() { - filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter) - containers, err := client.ListContainers(filter) - Expect(err).NotTo(HaveOccurred()) - Expect(len(containers) == 0).To(BeTrue()) + When("a filter matching nothing", func() { + It("should return an empty array", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter) + client := dockerClient{ + api: docker, + pullImages: false, + } + containers, err := client.ListContainers(filter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).To(BeEmpty()) + }) }) - }) - When("listing containers with a watchtower filter", func() { - It("should return only the watchtower container", func() { - containers, err := client.ListContainers(filters.WatchtowerContainersFilter) - Expect(err).NotTo(HaveOccurred()) - Expect(len(containers) == 1).To(BeTrue()) - Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest")) + When("a watchtower filter is provided", func() { + It("should return only the watchtower container", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + client := dockerClient{ + api: docker, + pullImages: false, + } + containers, err := client.ListContainers(filters.WatchtowerContainersFilter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).To(ConsistOf(withContainerImageName(Equal("containrrr/watchtower:latest")))) + }) }) - }) - When(`listing containers with the "include stopped" option`, func() { - It("should return both stopped and running containers", func() { - client = dockerClient{ - api: docker, - pullImages: false, - includeStopped: true, - } - containers, err := client.ListContainers(filters.NoFilter) - Expect(err).NotTo(HaveOccurred()) - Expect(len(containers) > 0).To(BeTrue()) + When(`include stopped is enabled`, func() { + It("should return both stopped and running containers", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running", "exited", "created")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("stopped", "watchtower", "running")...) + client := dockerClient{ + api: docker, + pullImages: false, + includeStopped: true, + } + containers, err := client.ListContainers(filters.NoFilter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).To(ContainElement(havingRunningState(false))) + }) }) - }) - When(`listing containers with the "include restart" option`, func() { - It("should return both stopped, restarting and running containers", func() { - client = dockerClient{ - api: docker, - pullImages: false, - includeRestarting: true, - } - containers, err := client.ListContainers(filters.NoFilter) - Expect(err).NotTo(HaveOccurred()) - RestartingContainerFound := false - for _, ContainerRunning := range containers { - if ContainerRunning.containerInfo.State.Restarting { - RestartingContainerFound = true + When(`include restarting is enabled`, func() { + It("should return both restarting and running containers", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running", "restarting")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running", "restarting")...) + client := dockerClient{ + api: docker, + pullImages: false, + includeRestarting: true, } - } - Expect(RestartingContainerFound).To(BeTrue()) - Expect(RestartingContainerFound).NotTo(BeFalse()) + containers, err := client.ListContainers(filters.NoFilter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).To(ContainElement(havingRestartingState(true))) + }) }) - }) - When(`listing containers without restarting ones`, func() { - It("should not return restarting containers", func() { - client = dockerClient{ - api: docker, - pullImages: false, - includeRestarting: false, - } - containers, err := client.ListContainers(filters.NoFilter) - Expect(err).NotTo(HaveOccurred()) - RestartingContainerFound := false - for _, ContainerRunning := range containers { - if ContainerRunning.containerInfo.State.Restarting { - RestartingContainerFound = true + When(`include restarting is disabled`, func() { + It("should not return restarting containers", func() { + mockServer.AppendHandlers(mocks.ListContainersHandler("running")) + mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + client := dockerClient{ + api: docker, + pullImages: false, + includeRestarting: false, } - } - Expect(RestartingContainerFound).To(BeFalse()) - Expect(RestartingContainerFound).NotTo(BeTrue()) + containers, err := client.ListContainers(filters.NoFilter) + Expect(err).NotTo(HaveOccurred()) + Expect(containers).NotTo(ContainElement(havingRestartingState(true))) + }) }) }) Describe(`ExecuteCommand`, func() { When(`logging`, func() { It("should include container id field", func() { + client := dockerClient{ + api: docker, + pullImages: false, + } + // Capture logrus output in buffer logbuf := gbytes.NewBuffer() origOut := logrus.StandardLogger().Out defer logrus.SetOutput(origOut) logrus.SetOutput(logbuf) - _, err := client.ExecuteCommand("ex-cont-id", "exec-cmd", 1) + user := "" + containerID := t.ContainerID("ex-cont-id") + execID := "ex-exec-id" + cmd := "exec-cmd" + + mockServer.AppendHandlers( + // API.ContainerExecCreate + ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", HaveSuffix("containers/%v/exec", containerID)), + ghttp.VerifyJSONRepresenting(types.ExecConfig{ + User: user, + Detach: false, + Tty: true, + Cmd: []string{ + "sh", + "-c", + cmd, + }, + }), + ghttp.RespondWithJSONEncoded(http.StatusOK, types.IDResponse{ID: execID}), + ), + // API.ContainerExecStart + ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", HaveSuffix("exec/%v/start", execID)), + ghttp.VerifyJSONRepresenting(types.ExecStartCheck{ + Detach: false, + Tty: true, + }), + ghttp.RespondWith(http.StatusOK, nil), + ), + // API.ContainerExecInspect + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", HaveSuffix("exec/ex-exec-id/json")), + ghttp.RespondWithJSONEncoded(http.StatusOK, backend.ExecInspect{ + ID: execID, + Running: false, + ExitCode: nil, + ProcessConfig: &backend.ExecProcessConfig{ + Entrypoint: "sh", + Arguments: []string{"-c", cmd}, + User: user, + }, + ContainerID: string(containerID), + }), + ), + ) + + _, err := client.ExecuteCommand(containerID, cmd, 1) Expect(err).NotTo(HaveOccurred()) // Note: Since Execute requires opening up a raw TCP stream to the daemon for the output, this will fail // when using the mock API server. Regardless of the outcome, the log should include the container ID @@ -146,3 +213,25 @@ var _ = Describe("the client", func() { }) }) }) + +// Gomega matcher helpers + +func withContainerImageName(matcher GomegaMatcher) GomegaMatcher { + return WithTransform(containerImageName, matcher) +} + +func containerImageName(container Container) string { + return container.ImageName() +} + +func havingRestartingState(expected bool) GomegaMatcher { + return WithTransform(func(container Container) bool { + return container.containerInfo.State.Restarting + }, Equal(expected)) +} + +func havingRunningState(expected bool) GomegaMatcher { + return WithTransform(func(container Container) bool { + return container.containerInfo.State.Running + }, Equal(expected)) +} diff --git a/pkg/container/mocks/ApiServer.go b/pkg/container/mocks/ApiServer.go index 96c3921..22613cf 100644 --- a/pkg/container/mocks/ApiServer.go +++ b/pkg/container/mocks/ApiServer.go @@ -3,91 +3,123 @@ package mocks import ( "encoding/json" "fmt" - "github.com/onsi/ginkgo" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/ghttp" "io/ioutil" "net/http" - "net/http/httptest" + "net/url" "path/filepath" - "strings" - - "github.com/docker/docker/api/types" - "github.com/sirupsen/logrus" ) -// NewMockAPIServer returns a mocked docker api server that responds to some fixed requests -// used in the test suite. -func NewMockAPIServer() *httptest.Server { - return httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - logrus.Debug("Mock server has received a HTTP call on ", r.URL) - var response = "" +func getMockJSONFile(relPath string) ([]byte, error) { + absPath, _ := filepath.Abs(relPath) + buf, err := ioutil.ReadFile(absPath) + if err != nil { + // logrus.WithError(err).WithField("file", absPath).Error(err) + return nil, err + } + return buf, nil +} - if isRequestFor("filters=", r) { +func RespondWithJSONFile(relPath string, statusCode int, optionalHeader ...http.Header) http.HandlerFunc { + handler, err := respondWithJSONFile(relPath, statusCode, optionalHeader...) + ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) + return handler +} - Filters := r.URL.Query().Get("filters") - var result map[string]interface{} - _ = json.Unmarshal([]byte(Filters), &result) - status := result["status"].(map[string]interface{}) +func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...http.Header) (http.HandlerFunc, error) { + buf, err := getMockJSONFile(relPath) + if err != nil { + return nil, err + } + return ghttp.RespondWith(statusCode, buf, optionalHeader...), nil +} - response = getMockJSONFromDisk("./mocks/data/containers.json") - var x2 []types.Container - var containers []types.Container - _ = json.Unmarshal([]byte(response), &containers) - for _, v := range containers { - for key := range status { - if v.State == key { - x2 = append(x2, v) - } - } - } +func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc { + handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2) + for _, file := range containerFiles { + handlers = append(handlers, getContainerHandler(file)) - b, _ := json.Marshal(x2) - response = string(b) + // Also append the image request since that will be called for every container + if file == "running" { + // The "running" container is the only one using image02 + handlers = append(handlers, getImageHandler(1)) + } else { + handlers = append(handlers, getImageHandler(0)) + } + } + return handlers +} - } else if isRequestFor("containers/json?limit=0", r) { - response = getMockJSONFromDisk("./mocks/data/containers.json") - } else if isRequestFor("ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", r) { - response = getMockJSONFromDisk("./mocks/data/container_stopped.json") - } else if isRequestFor("b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", r) { - response = getMockJSONFromDisk("./mocks/data/container_running.json") - } else if isRequestFor("ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67", r) { - response = getMockJSONFromDisk("./mocks/data/container_restarting.json") - } else if isRequestFor("sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd", r) { - response = getMockJSONFromDisk("./mocks/data/image01.json") - } else if isRequestFor("sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", r) { - response = getMockJSONFromDisk("./mocks/data/image02.json") - } else if isRequestFor("containers/ex-cont-id/exec", r) { - response = `{"Id": "ex-exec-id"}` - } else if isRequestFor("exec/ex-exec-id/start", r) { - response = `{"Id": "ex-exec-id"}` - } else if isRequestFor("exec/ex-exec-id/json", r) { - response = `{ - "ExecID": "ex-exec-id", - "ContainerID": "ex-cont-id", - "Running": false, - "ExitCode": 0, - "Pid": 0 - }` - } else { - // Allow ginkgo to correctly capture the failed assertion, even though this is called from a go func - defer ginkgo.GinkgoRecover() - ginkgo.Fail(fmt.Sprintf("mock API server endpoint not supported: %q", r.URL.String())) - } - _, _ = fmt.Fprintln(w, response) - }, - )) +func createFilterArgs(statuses []string) filters.Args { + args := filters.NewArgs() + for _, status := range statuses { + args.Add("status", status) + } + return args } -func isRequestFor(urlPart string, r *http.Request) bool { - return strings.Contains(r.URL.String(), urlPart) +var containerFileIds = map[string]string{ + "stopped": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", + "watchtower": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134", + "running": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", + "restarting": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67", } -func getMockJSONFromDisk(relPath string) string { - absPath, _ := filepath.Abs(relPath) - buf, err := ioutil.ReadFile(absPath) - if err != nil { - logrus.WithError(err).WithField("file", absPath).Error(err) - return "" +var imageIds = []string{ + "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", + "sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd", +} + +func getContainerHandler(file string) http.HandlerFunc { + id, ok := containerFileIds[file] + failTestUnless(ok) + return ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", HaveSuffix("/containers/%v/json", id)), + RespondWithJSONFile(fmt.Sprintf("./mocks/data/container_%v.json", file), http.StatusOK), + ) +} + +func ListContainersHandler(statuses ...string) http.HandlerFunc { + filterArgs := createFilterArgs(statuses) + bytes, err := filterArgs.MarshalJSON() + ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) + query := url.Values{ + "limit": []string{"0"}, + "filters": []string{string(bytes)}, + } + return ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", HaveSuffix("containers/json"), query.Encode()), + respondWithFilteredContainers(filterArgs), + ) +} + +func respondWithFilteredContainers(filters filters.Args) http.HandlerFunc { + containersJson, err := getMockJSONFile("./mocks/data/containers.json") + ExpectWithOffset(2, err).ShouldNot(HaveOccurred()) + var filteredContainers []types.Container + var containers []types.Container + ExpectWithOffset(2, json.Unmarshal(containersJson, &containers)).To(Succeed()) + for _, v := range containers { + for _, key := range filters.Get("status") { + if v.State == key { + filteredContainers = append(filteredContainers, v) + } + } } - return string(buf) + + return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers) +} + +func getImageHandler(index int) http.HandlerFunc { + return ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", HaveSuffix("/images/%v/json", imageIds[index])), + RespondWithJSONFile(fmt.Sprintf("./mocks/data/image%02d.json", index+1), http.StatusOK), + ) +} + +func failTestUnless(ok bool) { + ExpectWithOffset(2, ok).To(BeTrue(), "test setup failed") } diff --git a/pkg/container/mocks/data/container_stopped.json b/pkg/container/mocks/data/container_stopped.json index a4519d7..0043c62 100644 --- a/pkg/container/mocks/data/container_stopped.json +++ b/pkg/container/mocks/data/container_stopped.json @@ -21,7 +21,7 @@ "HostnamePath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hostname", "HostsPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hosts", "LogPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65-json.log", - "Name": "/watchtower-test", + "Name": "/watchtower-stopped", "RestartCount": 0, "Driver": "overlay2", "Platform": "linux", diff --git a/pkg/container/mocks/data/container_watchtower.json b/pkg/container/mocks/data/container_watchtower.json new file mode 100644 index 0000000..14737b5 --- /dev/null +++ b/pkg/container/mocks/data/container_watchtower.json @@ -0,0 +1,205 @@ +{ + "Id": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134", + "Created": "2020-04-10T19:51:22.245041005Z", + "Path": "/watchtower", + "Args": [], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 3854, + "ExitCode": 0, + "Error": "", + "StartedAt": "2019-04-13T22:38:24.498745809Z", + "FinishedAt": "2019-04-13T22:38:18.486292076Z" + }, + "Image": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", + "ResolvConfPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hostname", + "HostsPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hosts", + "LogPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65-json.log", + "Name": "/watchtower-running", + "RestartCount": 0, + "Driver": "overlay2", + "Platform": "linux", + "MountLabel": "", + "ProcessLabel": "", + "AppArmorProfile": "", + "ExecIDs": null, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock" + ], + "ContainerIDFile": "", + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + "NetworkMode": "default", + "PortBindings": {}, + "RestartPolicy": { + "Name": "no", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "CapAdd": null, + "CapDrop": null, + "Dns": [], + "DnsOptions": [], + "DnsSearch": [], + "ExtraHosts": null, + "GroupAdd": null, + "IpcMode": "shareable", + "Cgroup": "", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": null, + "UTSMode": "", + "UsernsMode": "", + "ShmSize": 67108864, + "Runtime": "runc", + "ConsoleSize": [ + 0, + 0 + ], + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": [], + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": [], + "DeviceCgroupRules": null, + "DiskQuota": 0, + "KernelMemory": 0, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": null, + "OomKillDisable": false, + "PidsLimit": 0, + "Ulimits": null, + "CpuCount": 0, + "CpuPercent": 0, + "IOMaximumIOps": 0, + "IOMaximumBandwidth": 0, + "MaskedPaths": [ + "/proc/asound", + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware" + ], + "ReadonlyPaths": [ + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + }, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc-init/diff:/var/lib/docker/overlay2/cdf82f50bc49177d0c17c24f3eaa29eba607b70cc6a081f77781b21c59a13eb8/diff:/var/lib/docker/overlay2/8108325ee844603c9b08d2772cf6e65dccf31dd5171f265078e5ed79a0ba3c0f/diff:/var/lib/docker/overlay2/e5e0cce6bf91b829a308424d99d7e56a33be3a11414ff5cdc48e762a1342b20f/diff", + "MergedDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/merged", + "UpperDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/diff", + "WorkDir": "/var/lib/docker/overlay2/9f6b91ea6e142835035d91123bbc7a05224dfa2abd4d020eac42f2ab420ccddc/work" + }, + "Name": "overlay2" + }, + "Mounts": [ + { + "Type": "bind", + "Source": "/var/run/docker.sock", + "Destination": "/var/run/docker.sock", + "Mode": "", + "RW": true, + "Propagation": "rprivate" + } + ], + "Config": { + "Hostname": "ae8964ba86c7", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": null, + "Image": "containrrr/watchtower:latest", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/watchtower" + ], + "OnBuild": null, + "Labels": { + "com.centurylinklabs.watchtower": "true" + } + }, + "NetworkSettings": { + "Bridge": "", + "SandboxID": "05627d36c08ed994eebc44a2a8c9365a511756b55c500fb03fd5a14477cd4bf3", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": {}, + "SandboxKey": "/var/run/docker/netns/05627d36c08e", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": { + "bridge": { + "IPAMConfig": null, + "Links": null, + "Aliases": null, + "NetworkID": "8fcfd56fa9203bafa98510abb08bff66ad05bef5b6e97d158cbae3397e1e065e", + "EndpointID": "", + "Gateway": "", + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "", + "DriverOpts": null + } + } + } +} diff --git a/pkg/container/mocks/data/containers.json b/pkg/container/mocks/data/containers.json index 4acd7e2..439cc51 100644 --- a/pkg/container/mocks/data/containers.json +++ b/pkg/container/mocks/data/containers.json @@ -2,7 +2,7 @@ { "Id": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", "Names": [ - "/watchtower-test" + "/watchtower-stopped" ], "Image": "containrrr/watchtower:latest", "ImageID": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", @@ -12,7 +12,7 @@ "Labels": { "com.centurylinklabs.watchtower": "true" }, - "State": "running", + "State": "exited", "Status": "Exited (1) 6 days ago", "HostConfig": { "NetworkMode": "default" @@ -47,6 +47,54 @@ } ] }, + { + "Id": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134", + "Names": [ + "/watchtower-running" + ], + "Image": "containrrr/watchtower:latest", + "ImageID": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", + "Command": "/watchtower", + "Created": 1554925882, + "Ports": [], + "Labels": { + "com.centurylinklabs.watchtower": "true" + }, + "State": "running", + "Status": "Up 3 days", + "HostConfig": { + "NetworkMode": "default" + }, + "NetworkSettings": { + "Networks": { + "bridge": { + "IPAMConfig": null, + "Links": null, + "Aliases": null, + "NetworkID": "8fcfd56fa9203bafa98510abb08bff66ad05bef5b6e97d158cbae3397e1e065e", + "EndpointID": "", + "Gateway": "", + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "", + "DriverOpts": null + } + } + }, + "Mounts": [ + { + "Type": "bind", + "Source": "/var/run/docker.sock", + "Destination": "/var/run/docker.sock", + "Mode": "", + "RW": true, + "Propagation": "rprivate" + } + ] + }, { "Id": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", "Names": [