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": [