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
pull/1117/head
nils måsén 3 years ago
parent 3bf934027f
commit 26fba69169

@ -3,28 +3,33 @@ package container
import ( import (
"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"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
cli "github.com/docker/docker/client" cli "github.com/docker/docker/client"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/ghttp"
"github.com/sirupsen/logrus" "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 _ = Describe("the client", func() {
var docker *cli.Client var docker *cli.Client
var client Client var mockServer *ghttp.Server
BeforeSuite(func() { BeforeSuite(func() {
server := mocks.NewMockAPIServer() mockServer = ghttp.NewServer()
docker, _ = cli.NewClientWithOpts( docker, _ = cli.NewClientWithOpts(
cli.WithHost(server.URL), cli.WithHost(mockServer.URL()),
cli.WithHTTPClient(server.Client())) cli.WithHTTPClient(mockServer.HTTPTestServer.Client()))
client = dockerClient{
api: docker,
pullImages: false,
}
}) })
It("should return a client for the api", func() { AfterEach(func() {
Expect(client).NotTo(BeNil()) mockServer.Reset()
}) })
Describe("WarnOnHeadPullFailed", func() { Describe("WarnOnHeadPullFailed", func() {
containerUnknown := *mockContainerWithImageName("unknown.repo/prefix/imagename:latest") containerUnknown := *mockContainerWithImageName("unknown.repo/prefix/imagename:latest")
@ -54,90 +59,152 @@ var _ = Describe("the client", func() {
}) })
}) })
}) })
When("listing containers", func() {
When("listing containers without any filter", func() { When("no filter is provided", func() {
It("should return all available containers", 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) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 2).To(BeTrue()) Expect(containers).To(HaveLen(2))
}) })
}) })
When("listing containers with a filter matching nothing", func() { When("a filter matching nothing", func() {
It("should return an empty array", 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) filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
client := dockerClient{
api: docker,
pullImages: false,
}
containers, err := client.ListContainers(filter) containers, err := client.ListContainers(filter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 0).To(BeTrue()) Expect(containers).To(BeEmpty())
}) })
}) })
When("listing containers with a watchtower filter", func() { When("a watchtower filter is provided", func() {
It("should return only the watchtower container", 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) containers, err := client.ListContainers(filters.WatchtowerContainersFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) == 1).To(BeTrue()) Expect(containers).To(ConsistOf(withContainerImageName(Equal("containrrr/watchtower:latest"))))
Expect(containers[0].ImageName()).To(Equal("containrrr/watchtower:latest"))
}) })
}) })
When(`listing containers with the "include stopped" option`, func() { When(`include stopped is enabled`, func() {
It("should return both stopped and running containers", func() { It("should return both stopped and running containers", func() {
client = dockerClient{ mockServer.AppendHandlers(mocks.ListContainersHandler("running", "exited", "created"))
mockServer.AppendHandlers(mocks.GetContainerHandlers("stopped", "watchtower", "running")...)
client := dockerClient{
api: docker, api: docker,
pullImages: false, pullImages: false,
includeStopped: true, includeStopped: true,
} }
containers, err := client.ListContainers(filters.NoFilter) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(containers) > 0).To(BeTrue()) Expect(containers).To(ContainElement(havingRunningState(false)))
}) })
}) })
When(`listing containers with the "include restart" option`, func() { When(`include restarting is enabled`, func() {
It("should return both stopped, restarting and running containers", func() { It("should return both restarting and running containers", func() {
client = dockerClient{ mockServer.AppendHandlers(mocks.ListContainersHandler("running", "restarting"))
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running", "restarting")...)
client := dockerClient{
api: docker, api: docker,
pullImages: false, pullImages: false,
includeRestarting: true, includeRestarting: true,
} }
containers, err := client.ListContainers(filters.NoFilter) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
RestartingContainerFound := false Expect(containers).To(ContainElement(havingRestartingState(true)))
for _, ContainerRunning := range containers {
if ContainerRunning.containerInfo.State.Restarting {
RestartingContainerFound = true
}
}
Expect(RestartingContainerFound).To(BeTrue())
Expect(RestartingContainerFound).NotTo(BeFalse())
}) })
}) })
When(`listing containers without restarting ones`, func() { When(`include restarting is disabled`, func() {
It("should not return restarting containers", func() { It("should not return restarting containers", func() {
client = dockerClient{ mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...)
client := dockerClient{
api: docker, api: docker,
pullImages: false, pullImages: false,
includeRestarting: false, includeRestarting: false,
} }
containers, err := client.ListContainers(filters.NoFilter) containers, err := client.ListContainers(filters.NoFilter)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
RestartingContainerFound := false Expect(containers).NotTo(ContainElement(havingRestartingState(true)))
for _, ContainerRunning := range containers { })
if ContainerRunning.containerInfo.State.Restarting {
RestartingContainerFound = true
}
}
Expect(RestartingContainerFound).To(BeFalse())
Expect(RestartingContainerFound).NotTo(BeTrue())
}) })
}) })
Describe(`ExecuteCommand`, func() { Describe(`ExecuteCommand`, func() {
When(`logging`, func() { When(`logging`, func() {
It("should include container id field", func() { It("should include container id field", func() {
client := dockerClient{
api: docker,
pullImages: false,
}
// Capture logrus output in buffer // Capture logrus output in buffer
logbuf := gbytes.NewBuffer() logbuf := gbytes.NewBuffer()
origOut := logrus.StandardLogger().Out origOut := logrus.StandardLogger().Out
defer logrus.SetOutput(origOut) defer logrus.SetOutput(origOut)
logrus.SetOutput(logbuf) 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()) Expect(err).NotTo(HaveOccurred())
// Note: Since Execute requires opening up a raw TCP stream to the daemon for the output, this will fail // 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 // 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))
}

@ -3,91 +3,123 @@ package mocks
import ( import (
"encoding/json" "encoding/json"
"fmt" "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" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/url"
"path/filepath" "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 func getMockJSONFile(relPath string) ([]byte, error) {
// used in the test suite. absPath, _ := filepath.Abs(relPath)
func NewMockAPIServer() *httptest.Server { buf, err := ioutil.ReadFile(absPath)
return httptest.NewServer(http.HandlerFunc( if err != nil {
func(w http.ResponseWriter, r *http.Request) { // logrus.WithError(err).WithField("file", absPath).Error(err)
logrus.Debug("Mock server has received a HTTP call on ", r.URL) return nil, err
var response = "" }
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") func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...http.Header) (http.HandlerFunc, error) {
var result map[string]interface{} buf, err := getMockJSONFile(relPath)
_ = json.Unmarshal([]byte(Filters), &result) if err != nil {
status := result["status"].(map[string]interface{}) return nil, err
}
return ghttp.RespondWith(statusCode, buf, optionalHeader...), nil
}
func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc {
handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2)
for _, file := range containerFiles {
handlers = append(handlers, getContainerHandler(file))
response = getMockJSONFromDisk("./mocks/data/containers.json") // Also append the image request since that will be called for every container
var x2 []types.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
}
func createFilterArgs(statuses []string) filters.Args {
args := filters.NewArgs()
for _, status := range statuses {
args.Add("status", status)
}
return args
}
var containerFileIds = map[string]string{
"stopped": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
"watchtower": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134",
"running": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
"restarting": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67",
}
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 var containers []types.Container
_ = json.Unmarshal([]byte(response), &containers) ExpectWithOffset(2, json.Unmarshal(containersJson, &containers)).To(Succeed())
for _, v := range containers { for _, v := range containers {
for key := range status { for _, key := range filters.Get("status") {
if v.State == key { if v.State == key {
x2 = append(x2, v) filteredContainers = append(filteredContainers, v)
} }
} }
} }
b, _ := json.Marshal(x2) return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers)
response = string(b)
} 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 isRequestFor(urlPart string, r *http.Request) bool { func getImageHandler(index int) http.HandlerFunc {
return strings.Contains(r.URL.String(), urlPart) 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 getMockJSONFromDisk(relPath string) string { func failTestUnless(ok bool) {
absPath, _ := filepath.Abs(relPath) ExpectWithOffset(2, ok).To(BeTrue(), "test setup failed")
buf, err := ioutil.ReadFile(absPath)
if err != nil {
logrus.WithError(err).WithField("file", absPath).Error(err)
return ""
}
return string(buf)
} }

@ -21,7 +21,7 @@
"HostnamePath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hostname", "HostnamePath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hostname",
"HostsPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hosts", "HostsPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/hosts",
"LogPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65-json.log", "LogPath": "/var/lib/docker/containers/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65/ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65-json.log",
"Name": "/watchtower-test", "Name": "/watchtower-stopped",
"RestartCount": 0, "RestartCount": 0,
"Driver": "overlay2", "Driver": "overlay2",
"Platform": "linux", "Platform": "linux",

@ -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
}
}
}
}

@ -2,7 +2,7 @@
{ {
"Id": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", "Id": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
"Names": [ "Names": [
"/watchtower-test" "/watchtower-stopped"
], ],
"Image": "containrrr/watchtower:latest", "Image": "containrrr/watchtower:latest",
"ImageID": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", "ImageID": "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa",
@ -12,7 +12,7 @@
"Labels": { "Labels": {
"com.centurylinklabs.watchtower": "true" "com.centurylinklabs.watchtower": "true"
}, },
"State": "running", "State": "exited",
"Status": "Exited (1) 6 days ago", "Status": "Exited (1) 6 days ago",
"HostConfig": { "HostConfig": {
"NetworkMode": "default" "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", "Id": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
"Names": [ "Names": [

Loading…
Cancel
Save