From dca45f50cb0d25ddce27f9ff5424d4fcc151f848 Mon Sep 17 00:00:00 2001 From: schizo99 Date: Tue, 8 Aug 2023 18:32:44 +0200 Subject: [PATCH] feat: support container network mode (#1429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: nils måsén Co-authored-by: Andreas Åhman --- .../container-networking/docker-compose.yml | 17 + docs/linked-containers.md | 4 +- pkg/container/client.go | 16 + pkg/container/client_test.go | 42 +- pkg/container/container.go | 7 + pkg/container/mocks/ApiServer.go | 141 +++++-- pkg/container/mocks/container_ref.go | 42 ++ ...ntainer_net_consumer-missing_supplier.json | 205 ++++++++++ .../mocks/data/container_net_consumer.json | 205 ++++++++++ .../mocks/data/container_net_supplier.json | 380 ++++++++++++++++++ .../data/{image01.json => image_default.json} | 0 .../mocks/data/image_net_consumer.json | 115 ++++++ .../mocks/data/image_net_producer.json | 210 ++++++++++ .../data/{image02.json => image_running.json} | 0 scripts/contnet-tests.sh | 42 ++ 15 files changed, 1379 insertions(+), 47 deletions(-) create mode 100644 dockerfiles/container-networking/docker-compose.yml create mode 100644 pkg/container/mocks/container_ref.go create mode 100644 pkg/container/mocks/data/container_net_consumer-missing_supplier.json create mode 100644 pkg/container/mocks/data/container_net_consumer.json create mode 100644 pkg/container/mocks/data/container_net_supplier.json rename pkg/container/mocks/data/{image01.json => image_default.json} (100%) create mode 100644 pkg/container/mocks/data/image_net_consumer.json create mode 100644 pkg/container/mocks/data/image_net_producer.json rename pkg/container/mocks/data/{image02.json => image_running.json} (100%) create mode 100755 scripts/contnet-tests.sh diff --git a/dockerfiles/container-networking/docker-compose.yml b/dockerfiles/container-networking/docker-compose.yml new file mode 100644 index 0000000..24cd00d --- /dev/null +++ b/dockerfiles/container-networking/docker-compose.yml @@ -0,0 +1,17 @@ +services: + producer: + image: qmcgaw/gluetun:v3.35.0 + cap_add: + - NET_ADMIN + environment: + - VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER} + - OPENVPN_USER=${OPENVPN_USER} + - OPENVPN_PASSWORD=${OPENVPN_PASSWORD} + - SERVER_COUNTRIES=${SERVER_COUNTRIES} + consumer: + depends_on: + - producer + image: nginx:1.25.1 + network_mode: "service:producer" + labels: + - "com.centurylinklabs.watchtower.depends-on=/wt-contnet-producer-1" diff --git a/docs/linked-containers.md b/docs/linked-containers.md index 240fb97..c7e9be8 100644 --- a/docs/linked-containers.md +++ b/docs/linked-containers.md @@ -2,4 +2,6 @@ Watchtower will detect if there are links between any of the running containers For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work. -If you want to override existing links you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma. +If you want to override existing links, or if you are not using links, you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma. + +When you have a depending container that is using `network_mode: service:container` then watchtower will treat that container as an implicit link. diff --git a/pkg/container/client.go b/pkg/container/client.go index e62923f..14ca237 100644 --- a/pkg/container/client.go +++ b/pkg/container/client.go @@ -157,6 +157,22 @@ func (client dockerClient) GetContainer(containerID t.ContainerID) (t.Container, return &Container{}, err } + netType, netContainerId, found := strings.Cut(string(containerInfo.HostConfig.NetworkMode), ":") + if found && netType == "container" { + parentContainer, err := client.api.ContainerInspect(bg, netContainerId) + if err != nil { + log.WithFields(map[string]interface{}{ + "container": containerInfo.Name, + "error": err, + "network-container": netContainerId, + }).Warnf("Unable to resolve network container: %v", err) + + } else { + // Replace the container ID with a container name to allow it to reference the re-created network container + containerInfo.HostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", parentContainer.Name)) + } + } + imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image) if err != nil { log.Warnf("Failed to retrieve container image info: %v", err) diff --git a/pkg/container/client_test.go b/pkg/container/client_test.go index 6600123..e8fcca2 100644 --- a/pkg/container/client_test.go +++ b/pkg/container/client_test.go @@ -141,7 +141,7 @@ var _ = Describe("the client", 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")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...) client := dockerClient{ api: docker, ClientOptions: ClientOptions{PullImages: false}, @@ -154,7 +154,7 @@ var _ = Describe("the client", func() { When("a filter matching nothing", func() { It("should return an empty array", func() { mockServer.AppendHandlers(mocks.ListContainersHandler("running")) - mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...) filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter) client := dockerClient{ api: docker, @@ -168,7 +168,7 @@ var _ = Describe("the client", func() { 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")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...) client := dockerClient{ api: docker, ClientOptions: ClientOptions{PullImages: false}, @@ -181,7 +181,7 @@ var _ = Describe("the client", func() { 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")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Stopped, &mocks.Watchtower, &mocks.Running)...) client := dockerClient{ api: docker, ClientOptions: ClientOptions{PullImages: false, IncludeStopped: true}, @@ -194,7 +194,7 @@ var _ = Describe("the client", func() { 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")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running, &mocks.Restarting)...) client := dockerClient{ api: docker, ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: true}, @@ -207,7 +207,7 @@ var _ = Describe("the client", func() { When(`include restarting is disabled`, func() { It("should not return restarting containers", func() { mockServer.AppendHandlers(mocks.ListContainersHandler("running")) - mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...) + mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...) client := dockerClient{ api: docker, ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: false}, @@ -217,6 +217,36 @@ var _ = Describe("the client", func() { Expect(containers).NotTo(ContainElement(havingRestartingState(true))) }) }) + When(`a container uses container network mode`, func() { + When(`the network container can be resolved`, func() { + It("should return the container name instead of the ID", func() { + consumerContainerRef := mocks.NetConsumerOK + mockServer.AppendHandlers(mocks.GetContainerHandlers(&consumerContainerRef)...) + client := dockerClient{ + api: docker, + ClientOptions: ClientOptions{PullImages: false}, + } + container, err := client.GetContainer(consumerContainerRef.ContainerID()) + Expect(err).NotTo(HaveOccurred()) + networkMode := container.ContainerInfo().HostConfig.NetworkMode + Expect(networkMode.ConnectedContainer()).To(Equal(mocks.NetSupplierContainerName)) + }) + }) + When(`the network container cannot be resolved`, func() { + It("should still return the container ID", func() { + consumerContainerRef := mocks.NetConsumerInvalidSupplier + mockServer.AppendHandlers(mocks.GetContainerHandlers(&consumerContainerRef)...) + client := dockerClient{ + api: docker, + ClientOptions: ClientOptions{PullImages: false}, + } + container, err := client.GetContainer(consumerContainerRef.ContainerID()) + Expect(err).NotTo(HaveOccurred()) + networkMode := container.ContainerInfo().HostConfig.NetworkMode + Expect(networkMode.ConnectedContainer()).To(Equal(mocks.NetSupplierNotFoundID)) + }) + }) + }) }) Describe(`ExecuteCommand`, func() { When(`logging`, func() { diff --git a/pkg/container/container.go b/pkg/container/container.go index 20ae2e0..0f78f62 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -196,6 +196,13 @@ func (c Container) Links() []string { name := strings.Split(link, ":")[0] links = append(links, name) } + + // If the container uses another container for networking, it can be considered an implicit link + // since the container would stop working if the network supplier were to be recreated + networkMode := c.containerInfo.HostConfig.NetworkMode + if networkMode.IsContainer() { + links = append(links, networkMode.ConnectedContainer()) + } } return links diff --git a/pkg/container/mocks/ApiServer.go b/pkg/container/mocks/ApiServer.go index 652bafb..84756f0 100644 --- a/pkg/container/mocks/ApiServer.go +++ b/pkg/container/mocks/ApiServer.go @@ -3,12 +3,15 @@ package mocks import ( "encoding/json" "fmt" - "io/ioutil" + "github.com/onsi/ginkgo" "net/http" "net/url" + "os" "path/filepath" "strings" + t "github.com/containrrr/watchtower/pkg/types" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" O "github.com/onsi/gomega" @@ -17,10 +20,9 @@ import ( func getMockJSONFile(relPath string) ([]byte, error) { absPath, _ := filepath.Abs(relPath) - buf, err := ioutil.ReadFile(absPath) + buf, err := os.ReadFile(absPath) if err != nil { - // logrus.WithError(err).WithField("file", absPath).Error(err) - return nil, err + return nil, fmt.Errorf("mock JSON file %q not found: %e", absPath, err) } return buf, nil } @@ -41,19 +43,22 @@ func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...http. } // GetContainerHandlers returns the handlers serving lookups for the supplied container mock files -func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc { - handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2) - for _, file := range containerFiles { - handlers = append(handlers, getContainerFileHandler(file)) +func GetContainerHandlers(containerRefs ...*ContainerRef) []http.HandlerFunc { + handlers := make([]http.HandlerFunc, 0, len(containerRefs)*3) + for _, containerRef := range containerRefs { + handlers = append(handlers, getContainerFileHandler(containerRef)) - // 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, getImageFileHandler(1)) - } else { - handlers = append(handlers, getImageFileHandler(0)) + // Also append any containers that the container references, if any + for _, ref := range containerRef.references { + handlers = append(handlers, getContainerFileHandler(ref)) } + + // Also append the image request since that will be called for every container + handlers = append(handlers, getImageHandler(containerRef.image.id, + RespondWithJSONFile(containerRef.image.getFileName(), http.StatusOK), + )) } + return handlers } @@ -65,24 +70,90 @@ func createFilterArgs(statuses []string) filters.Args { return args } -var containerFileIds = map[string]string{ - "stopped": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", - "watchtower": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134", - "running": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", - "restarting": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67", +var defaultImage = imageRef{ + // watchtower + id: t.ImageID("sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa"), + file: "default", +} + +var Watchtower = ContainerRef{ + name: "watchtower", + id: "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134", + image: &defaultImage, +} +var Stopped = ContainerRef{ + name: "stopped", + id: "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65", + image: &defaultImage, +} +var Running = ContainerRef{ + name: "running", + id: "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008", + image: &imageRef{ + // portainer + id: t.ImageID("sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd"), + file: "running", + }, +} +var Restarting = ContainerRef{ + name: "restarting", + id: "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67", + image: &defaultImage, +} + +var netSupplierOK = ContainerRef{ + id: "25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2", + name: "net_supplier", + image: &imageRef{ + // gluetun + id: t.ImageID("sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51"), + file: "net_producer", + }, +} +var netSupplierNotFound = ContainerRef{ + id: NetSupplierNotFoundID, + name: netSupplierOK.name, + isMissing: true, +} + +// NetConsumerOK is used for testing `container` networking mode +// returns a container that consumes an existing supplier container +var NetConsumerOK = ContainerRef{ + id: "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6", + name: "net_consumer", + image: &imageRef{ + id: t.ImageID("sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8"), // nginx + file: "net_consumer", + }, + references: []*ContainerRef{&netSupplierOK}, } -var imageIds = []string{ - "sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa", - "sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd", +// NetConsumerInvalidSupplier is used for testing `container` networking mode +// returns a container that references a supplying container that does not exist +var NetConsumerInvalidSupplier = ContainerRef{ + id: NetConsumerOK.id, + name: "net_consumer-missing_supplier", + image: NetConsumerOK.image, + references: []*ContainerRef{&netSupplierNotFound}, } -func getContainerFileHandler(file string) http.HandlerFunc { - id, ok := containerFileIds[file] - failTestUnless(ok) +const NetSupplierNotFoundID = "badc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc" +const NetSupplierContainerName = "/wt-contnet-producer-1" + +func getContainerFileHandler(cr *ContainerRef) http.HandlerFunc { + + if cr.isMissing { + return containerNotFoundResponse(string(cr.id)) + } + + containerFile, err := cr.getContainerFile() + if err != nil { + ginkgo.Fail(fmt.Sprintf("Failed to get container mock file: %v", err)) + } + return getContainerHandler( - id, - RespondWithJSONFile(fmt.Sprintf("./mocks/data/container_%v.json", file), http.StatusOK), + string(cr.id), + RespondWithJSONFile(containerFile, http.StatusOK), ) } @@ -104,7 +175,7 @@ func GetContainerHandler(containerID string, containerInfo *types.ContainerJSON) // GetImageHandler mocks the GET images/{id}/json endpoint func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc { - return getImageHandler(imageInfo.ID, ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo)) + return getImageHandler(t.ImageID(imageInfo.ID), ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo)) } // ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses @@ -138,23 +209,13 @@ func respondWithFilteredContainers(filters filters.Args) http.HandlerFunc { return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers) } -func getImageHandler(imageId string, responseHandler http.HandlerFunc) http.HandlerFunc { +func getImageHandler(imageId t.ImageID, responseHandler http.HandlerFunc) http.HandlerFunc { return ghttp.CombineHandlers( 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), - ) -} - -func failTestUnless(ok bool) { - 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 @@ -180,7 +241,7 @@ func RemoveContainerHandler(containerID string, found FoundStatus) http.HandlerF } func containerNotFoundResponse(containerID string) http.HandlerFunc { - return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + containerID}) + return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + string(containerID)}) } var noContentStatusResponse = ghttp.RespondWith(http.StatusNoContent, nil) diff --git a/pkg/container/mocks/container_ref.go b/pkg/container/mocks/container_ref.go new file mode 100644 index 0000000..c46eb93 --- /dev/null +++ b/pkg/container/mocks/container_ref.go @@ -0,0 +1,42 @@ +package mocks + +import ( + "fmt" + "os" + + t "github.com/containrrr/watchtower/pkg/types" +) + +type imageRef struct { + id t.ImageID + file string +} + +func (ir *imageRef) getFileName() string { + return fmt.Sprintf("./mocks/data/image_%v.json", ir.file) +} + +type ContainerRef struct { + name string + id t.ContainerID + image *imageRef + file string + references []*ContainerRef + isMissing bool +} + +func (cr *ContainerRef) getContainerFile() (containerFile string, err error) { + file := cr.file + if file == "" { + file = cr.name + } + + containerFile = fmt.Sprintf("./mocks/data/container_%v.json", file) + _, err = os.Stat(containerFile) + + return containerFile, err +} + +func (cr *ContainerRef) ContainerID() t.ContainerID { + return cr.id +} diff --git a/pkg/container/mocks/data/container_net_consumer-missing_supplier.json b/pkg/container/mocks/data/container_net_consumer-missing_supplier.json new file mode 100644 index 0000000..c1a233b --- /dev/null +++ b/pkg/container/mocks/data/container_net_consumer-missing_supplier.json @@ -0,0 +1,205 @@ +{ + "Id": "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6", + "Created": "2023-07-25T14:55:14.69155887Z", + "Path": "/docker-entrypoint.sh", + "Args": [ + "nginx", + "-g", + "daemon off;" + ], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 3743, + "ExitCode": 0, + "Error": "", + "StartedAt": "2023-07-25T14:55:15.299654437Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + "Image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8", + "ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname", + "HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts", + "LogPath": "/var/lib/docker/containers/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6-json.log", + "Name": "/wt-contnet-consumer-1", + "RestartCount": 0, + "Driver": "overlay2", + "Platform": "linux", + "MountLabel": "", + "ProcessLabel": "", + "AppArmorProfile": "", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + "NetworkMode": "container:badc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc", + "PortBindings": {}, + "RestartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "ConsoleSize": [ + 0, + 0 + ], + "CapAdd": null, + "CapDrop": null, + "CgroupnsMode": "host", + "Dns": null, + "DnsOptions": null, + "DnsSearch": null, + "ExtraHosts": [], + "GroupAdd": null, + "IpcMode": "private", + "Cgroup": "", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": null, + "UTSMode": "", + "UsernsMode": "", + "ShmSize": 67108864, + "Runtime": "runc", + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": null, + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": null, + "DeviceCgroupRules": null, + "DeviceRequests": null, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": null, + "OomKillDisable": false, + "PidsLimit": null, + "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/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2-init/diff:/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff:/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff", + "MergedDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/merged", + "UpperDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/diff", + "WorkDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/work" + }, + "Name": "overlay2" + }, + "Mounts": [], + "Config": { + "Hostname": "25e75393800b", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "ExposedPorts": { + "80/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "NGINX_VERSION=1.23.3", + "NJS_VERSION=0.7.9", + "PKG_RELEASE=1~bullseye" + ], + "Cmd": [ + "nginx", + "-g", + "daemon off;" + ], + "Image": "nginx", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/docker-entrypoint.sh" + ], + "OnBuild": null, + "Labels": { + "com.docker.compose.config-hash": "8bb0e1c8c61f6d495840ba9133ebfb1e4ffda3e1adb701a011b03951848bb9fa", + "com.docker.compose.container-number": "1", + "com.docker.compose.depends_on": "producer:service_started:false", + "com.docker.compose.image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "wt-contnet", + "com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml", + "com.docker.compose.project.working_dir": "/tmp/wt-contnet", + "com.docker.compose.replace": "07bb70608f96f577aa02b9f317500e23e691c94eb099f6fb52301dfb031d0668", + "com.docker.compose.service": "consumer", + "com.docker.compose.version": "2.19.1", + "desktop.docker.io/wsl-distro": "Ubuntu", + "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e" + }, + "StopSignal": "SIGQUIT" + }, + "NetworkSettings": { + "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": {}, + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": {} + } +} diff --git a/pkg/container/mocks/data/container_net_consumer.json b/pkg/container/mocks/data/container_net_consumer.json new file mode 100644 index 0000000..2e64f89 --- /dev/null +++ b/pkg/container/mocks/data/container_net_consumer.json @@ -0,0 +1,205 @@ +{ + "Id": "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6", + "Created": "2023-07-25T14:55:14.69155887Z", + "Path": "/docker-entrypoint.sh", + "Args": [ + "nginx", + "-g", + "daemon off;" + ], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 3743, + "ExitCode": 0, + "Error": "", + "StartedAt": "2023-07-25T14:55:15.299654437Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + "Image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8", + "ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname", + "HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts", + "LogPath": "/var/lib/docker/containers/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6-json.log", + "Name": "/wt-contnet-consumer-1", + "RestartCount": 0, + "Driver": "overlay2", + "Platform": "linux", + "MountLabel": "", + "ProcessLabel": "", + "AppArmorProfile": "", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + "NetworkMode": "container:25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2", + "PortBindings": {}, + "RestartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "ConsoleSize": [ + 0, + 0 + ], + "CapAdd": null, + "CapDrop": null, + "CgroupnsMode": "host", + "Dns": null, + "DnsOptions": null, + "DnsSearch": null, + "ExtraHosts": [], + "GroupAdd": null, + "IpcMode": "private", + "Cgroup": "", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": null, + "UTSMode": "", + "UsernsMode": "", + "ShmSize": 67108864, + "Runtime": "runc", + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": null, + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": null, + "DeviceCgroupRules": null, + "DeviceRequests": null, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": null, + "OomKillDisable": false, + "PidsLimit": null, + "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/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2-init/diff:/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff:/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff", + "MergedDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/merged", + "UpperDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/diff", + "WorkDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/work" + }, + "Name": "overlay2" + }, + "Mounts": [], + "Config": { + "Hostname": "25e75393800b", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "ExposedPorts": { + "80/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "NGINX_VERSION=1.23.3", + "NJS_VERSION=0.7.9", + "PKG_RELEASE=1~bullseye" + ], + "Cmd": [ + "nginx", + "-g", + "daemon off;" + ], + "Image": "nginx", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/docker-entrypoint.sh" + ], + "OnBuild": null, + "Labels": { + "com.docker.compose.config-hash": "8bb0e1c8c61f6d495840ba9133ebfb1e4ffda3e1adb701a011b03951848bb9fa", + "com.docker.compose.container-number": "1", + "com.docker.compose.depends_on": "producer:service_started:false", + "com.docker.compose.image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "wt-contnet", + "com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml", + "com.docker.compose.project.working_dir": "/tmp/wt-contnet", + "com.docker.compose.replace": "07bb70608f96f577aa02b9f317500e23e691c94eb099f6fb52301dfb031d0668", + "com.docker.compose.service": "consumer", + "com.docker.compose.version": "2.19.1", + "desktop.docker.io/wsl-distro": "Ubuntu", + "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e" + }, + "StopSignal": "SIGQUIT" + }, + "NetworkSettings": { + "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": {}, + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": {} + } +} diff --git a/pkg/container/mocks/data/container_net_supplier.json b/pkg/container/mocks/data/container_net_supplier.json new file mode 100644 index 0000000..24db841 --- /dev/null +++ b/pkg/container/mocks/data/container_net_supplier.json @@ -0,0 +1,380 @@ +{ + "Id": "25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2", + "Created": "2023-07-25T14:55:14.595662628Z", + "Path": "/gluetun-entrypoint", + "Args": [], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 3648, + "ExitCode": 0, + "Error": "", + "StartedAt": "2023-07-25T14:55:15.193430103Z", + "FinishedAt": "0001-01-01T00:00:00Z", + "Health": { + "Status": "healthy", + "FailingStreak": 0, + "Log": [ + { + "Start": "2023-07-25T15:00:32.078491228Z", + "End": "2023-07-25T15:00:32.194554876Z", + "ExitCode": 0, + "Output": "" + }, + { + "Start": "2023-07-25T15:00:37.199245496Z", + "End": "2023-07-25T15:00:37.294845687Z", + "ExitCode": 0, + "Output": "" + }, + { + "Start": "2023-07-25T15:00:42.299676089Z", + "End": "2023-07-25T15:00:42.384213818Z", + "ExitCode": 0, + "Output": "" + }, + { + "Start": "2023-07-25T15:00:47.389142447Z", + "End": "2023-07-25T15:00:47.514483294Z", + "ExitCode": 0, + "Output": "" + }, + { + "Start": "2023-07-25T15:00:52.518770886Z", + "End": "2023-07-25T15:00:52.644288742Z", + "ExitCode": 0, + "Output": "" + } + ] + } + }, + "Image": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51", + "ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname", + "HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts", + "LogPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2-json.log", + "Name": "/wt-contnet-producer-1", + "RestartCount": 0, + "Driver": "overlay2", + "Platform": "linux", + "MountLabel": "", + "ProcessLabel": "", + "AppArmorProfile": "", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + "NetworkMode": "wt-contnet_default", + "PortBindings": {}, + "RestartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "ConsoleSize": [ + 0, + 0 + ], + "CapAdd": [ + "NET_ADMIN" + ], + "CapDrop": null, + "CgroupnsMode": "host", + "Dns": null, + "DnsOptions": null, + "DnsSearch": null, + "ExtraHosts": [], + "GroupAdd": null, + "IpcMode": "private", + "Cgroup": "", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": null, + "UTSMode": "", + "UsernsMode": "", + "ShmSize": 67108864, + "Runtime": "runc", + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": null, + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": null, + "DeviceCgroupRules": null, + "DeviceRequests": null, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": null, + "OomKillDisable": false, + "PidsLimit": null, + "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/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2-init/diff:/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/diff:/var/lib/docker/overlay2/a20c9490a23ee8af51898892d9bf32258d44e0e07f3799475be8e8f273a50f73/diff:/var/lib/docker/overlay2/d4c97f367c37c6ada9de57f438a3e19cc714be2a54a6f582a03de9e42d88b344/diff", + "MergedDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/merged", + "UpperDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/diff", + "WorkDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/work" + }, + "Name": "overlay2" + }, + "Mounts": [], + "Config": { + "Hostname": "25e75393800b", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "ExposedPorts": { + "8000/tcp": {}, + "8388/tcp": {}, + "8388/udp": {}, + "8888/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "OPENVPN_PASSWORD=", + "SERVER_COUNTRIES=Sweden", + "VPN_SERVICE_PROVIDER=nordvpn", + "OPENVPN_USER=", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "VPN_TYPE=openvpn", + "VPN_ENDPOINT_IP=", + "VPN_ENDPOINT_PORT=", + "VPN_INTERFACE=tun0", + "OPENVPN_PROTOCOL=udp", + "OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user", + "OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password", + "OPENVPN_VERSION=2.5", + "OPENVPN_VERBOSITY=1", + "OPENVPN_FLAGS=", + "OPENVPN_CIPHERS=", + "OPENVPN_AUTH=", + "OPENVPN_PROCESS_USER=root", + "OPENVPN_CUSTOM_CONFIG=", + "WIREGUARD_PRIVATE_KEY=", + "WIREGUARD_PRESHARED_KEY=", + "WIREGUARD_PUBLIC_KEY=", + "WIREGUARD_ALLOWED_IPS=", + "WIREGUARD_ADDRESSES=", + "WIREGUARD_MTU=1400", + "WIREGUARD_IMPLEMENTATION=auto", + "SERVER_REGIONS=", + "SERVER_CITIES=", + "SERVER_HOSTNAMES=", + "ISP=", + "OWNED_ONLY=no", + "PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET=", + "VPN_PORT_FORWARDING=off", + "VPN_PORT_FORWARDING_PROVIDER=", + "VPN_PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port", + "OPENVPN_CERT=", + "OPENVPN_KEY=", + "OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt", + "OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey", + "OPENVPN_ENCRYPTED_KEY=", + "OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key", + "OPENVPN_KEY_PASSPHRASE=", + "OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase", + "SERVER_NUMBER=", + "SERVER_NAMES=", + "FREE_ONLY=", + "MULTIHOP_ONLY=", + "PREMIUM_ONLY=", + "FIREWALL=on", + "FIREWALL_VPN_INPUT_PORTS=", + "FIREWALL_INPUT_PORTS=", + "FIREWALL_OUTBOUND_SUBNETS=", + "FIREWALL_DEBUG=off", + "LOG_LEVEL=info", + "HEALTH_SERVER_ADDRESS=127.0.0.1:9999", + "HEALTH_TARGET_ADDRESS=cloudflare.com:443", + "HEALTH_SUCCESS_WAIT_DURATION=5s", + "HEALTH_VPN_DURATION_INITIAL=6s", + "HEALTH_VPN_DURATION_ADDITION=5s", + "DOT=on", + "DOT_PROVIDERS=cloudflare", + "DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112", + "DOT_VERBOSITY=1", + "DOT_VERBOSITY_DETAILS=0", + "DOT_VALIDATION_LOGLEVEL=0", + "DOT_CACHING=on", + "DOT_IPV6=off", + "BLOCK_MALICIOUS=on", + "BLOCK_SURVEILLANCE=off", + "BLOCK_ADS=off", + "UNBLOCK=", + "DNS_UPDATE_PERIOD=24h", + "DNS_ADDRESS=127.0.0.1", + "DNS_KEEP_NAMESERVER=off", + "HTTPPROXY=", + "HTTPPROXY_LOG=off", + "HTTPPROXY_LISTENING_ADDRESS=:8888", + "HTTPPROXY_STEALTH=off", + "HTTPPROXY_USER=", + "HTTPPROXY_PASSWORD=", + "HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user", + "HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password", + "SHADOWSOCKS=off", + "SHADOWSOCKS_LOG=off", + "SHADOWSOCKS_LISTENING_ADDRESS=:8388", + "SHADOWSOCKS_PASSWORD=", + "SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password", + "SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305", + "HTTP_CONTROL_SERVER_LOG=on", + "HTTP_CONTROL_SERVER_ADDRESS=:8000", + "UPDATER_PERIOD=0", + "UPDATER_MIN_RATIO=0.8", + "UPDATER_VPN_SERVICE_PROVIDERS=", + "PUBLICIP_FILE=/tmp/gluetun/ip", + "PUBLICIP_PERIOD=12h", + "PPROF_ENABLED=no", + "PPROF_BLOCK_PROFILE_RATE=0", + "PPROF_MUTEX_PROFILE_RATE=0", + "PPROF_HTTP_SERVER_ADDRESS=:6060", + "VERSION_INFORMATION=on", + "TZ=", + "PUID=", + "PGID=" + ], + "Cmd": null, + "Healthcheck": { + "Test": [ + "CMD-SHELL", + "/gluetun-entrypoint healthcheck" + ], + "Interval": 5000000000, + "Timeout": 5000000000, + "StartPeriod": 10000000000, + "Retries": 1 + }, + "Image": "qmcgaw/gluetun", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/gluetun-entrypoint" + ], + "OnBuild": null, + "Labels": { + "com.docker.compose.config-hash": "6dc7dc42a86edb47039de3650a9cb9bdcf4866c113b8f9d797722c9dfd20428b", + "com.docker.compose.container-number": "1", + "com.docker.compose.depends_on": "", + "com.docker.compose.image": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "wt-contnet", + "com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml", + "com.docker.compose.project.working_dir": "/tmp/wt-contnet", + "com.docker.compose.replace": "9bd1ce000be81819fc915aa60a1674c7573b59a26ac4643ecf427a5732b9785f", + "com.docker.compose.service": "producer", + "com.docker.compose.version": "2.19.1", + "desktop.docker.io/wsl-distro": "Ubuntu", + "org.opencontainers.image.authors": "quentin.mcgaw@gmail.com", + "org.opencontainers.image.created": "2023-07-22T16:07:05.641Z", + "org.opencontainers.image.description": "VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.", + "org.opencontainers.image.documentation": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.licenses": "MIT", + "org.opencontainers.image.revision": "eecfb3952f202c0de3867d88e96d80c6b0f48359", + "org.opencontainers.image.source": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.title": "gluetun", + "org.opencontainers.image.url": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.version": "latest" + } + }, + "NetworkSettings": { + "Bridge": "", + "SandboxID": "34a321b64bb1b15f994dfccff0e235f881504f240c2028876ff6683962eaa10e", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": { + "8000/tcp": null, + "8388/tcp": null, + "8388/udp": null, + "8888/tcp": null + }, + "SandboxKey": "/var/run/docker/netns/34a321b64bb1", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": { + "wt-contnet_default": { + "IPAMConfig": null, + "Links": null, + "Aliases": [ + "wt-contnet-producer-1", + "producer", + "25e75393800b" + ], + "NetworkID": "f0f652a79efc54bcad52aafb4cbcc3b5dce1acaf11b172d8678d25f665faf63d", + "EndpointID": "2429c2b5d08db6c986bbd419a52ca4dd352715d80c5aeae04742efb84b0356fc", + "Gateway": "172.19.0.1", + "IPAddress": "172.19.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "02:42:ac:13:00:02", + "DriverOpts": null + } + } + } +} diff --git a/pkg/container/mocks/data/image01.json b/pkg/container/mocks/data/image_default.json similarity index 100% rename from pkg/container/mocks/data/image01.json rename to pkg/container/mocks/data/image_default.json diff --git a/pkg/container/mocks/data/image_net_consumer.json b/pkg/container/mocks/data/image_net_consumer.json new file mode 100644 index 0000000..add6edf --- /dev/null +++ b/pkg/container/mocks/data/image_net_consumer.json @@ -0,0 +1,115 @@ +{ + "Id": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8", + "RepoTags": [ + "nginx:latest" + ], + "RepoDigests": [ + "nginx@sha256:aa0afebbb3cfa473099a62c4b32e9b3fb73ed23f2a75a65ce1d4b4f55a5c2ef2" + ], + "Parent": "", + "Comment": "", + "Created": "2023-03-01T18:43:12.914398123Z", + "Container": "71a4c9a59d252d7c54812429bfe5df477e54e91ebfff1939ae39ecdf055d445c", + "ContainerConfig": { + "Hostname": "71a4c9a59d25", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "ExposedPorts": { + "80/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "NGINX_VERSION=1.23.3", + "NJS_VERSION=0.7.9", + "PKG_RELEASE=1~bullseye" + ], + "Cmd": [ + "/bin/sh", + "-c", + "#(nop) ", + "CMD [\"nginx\" \"-g\" \"daemon off;\"]" + ], + "Image": "sha256:6716b8a33f73b21e193bb63424ea1105eaaa6a8237fefe75570bea18c87a1711", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/docker-entrypoint.sh" + ], + "OnBuild": null, + "Labels": { + "maintainer": "NGINX Docker Maintainers " + }, + "StopSignal": "SIGQUIT" + }, + "DockerVersion": "20.10.23", + "Author": "", + "Config": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "ExposedPorts": { + "80/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "NGINX_VERSION=1.23.3", + "NJS_VERSION=0.7.9", + "PKG_RELEASE=1~bullseye" + ], + "Cmd": [ + "nginx", + "-g", + "daemon off;" + ], + "Image": "sha256:6716b8a33f73b21e193bb63424ea1105eaaa6a8237fefe75570bea18c87a1711", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/docker-entrypoint.sh" + ], + "OnBuild": null, + "Labels": { + "maintainer": "NGINX Docker Maintainers " + }, + "StopSignal": "SIGQUIT" + }, + "Architecture": "amd64", + "Os": "linux", + "Size": 141838643, + "VirtualSize": 141838643, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff", + "MergedDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/merged", + "UpperDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff", + "WorkDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/work" + }, + "Name": "overlay2" + }, + "RootFS": { + "Type": "layers", + "Layers": [ + "sha256:650abce4b096b06ac8bec2046d821d66d801af34f1f1d4c5e272ad030c7873db", + "sha256:4dc5cd799a08ff49a603870c8378ea93083bfc2a4176f56e5531997e94c195d0", + "sha256:e161c82b34d21179db1f546c1cd84153d28a17d865ccaf2dedeb06a903fec12c", + "sha256:83ba6d8ffb8c2974174c02d3ba549e7e0656ebb1bc075a6b6ee89b6c609c6a71", + "sha256:d8466e142d8710abf5b495ebb536478f7e19d9d03b151b5d5bd09df4cfb49248", + "sha256:101af4ba983b04be266217ecee414e88b23e394f62e9801c7c1bdb37cb37bcaa" + ] + }, + "Metadata": { + "LastTagTime": "0001-01-01T00:00:00Z" + } +} diff --git a/pkg/container/mocks/data/image_net_producer.json b/pkg/container/mocks/data/image_net_producer.json new file mode 100644 index 0000000..563ad95 --- /dev/null +++ b/pkg/container/mocks/data/image_net_producer.json @@ -0,0 +1,210 @@ +{ + "Id": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51", + "RepoTags": [ + "qmcgaw/gluetun:latest" + ], + "RepoDigests": [ + "qmcgaw/gluetun@sha256:cd532bf4ef88a348a915c6dc62a9867a2eca89aa70559b0b4a1ea15cc0e595d1" + ], + "Parent": "", + "Comment": "buildkit.dockerfile.v0", + "Created": "2023-07-22T16:10:29.457146856Z", + "Container": "", + "ContainerConfig": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": null, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": null + }, + "DockerVersion": "", + "Author": "", + "Config": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "ExposedPorts": { + "8000/tcp": {}, + "8388/tcp": {}, + "8388/udp": {}, + "8888/tcp": {} + }, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "VPN_SERVICE_PROVIDER=pia", + "VPN_TYPE=openvpn", + "VPN_ENDPOINT_IP=", + "VPN_ENDPOINT_PORT=", + "VPN_INTERFACE=tun0", + "OPENVPN_PROTOCOL=udp", + "OPENVPN_USER=", + "OPENVPN_PASSWORD=", + "OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user", + "OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password", + "OPENVPN_VERSION=2.5", + "OPENVPN_VERBOSITY=1", + "OPENVPN_FLAGS=", + "OPENVPN_CIPHERS=", + "OPENVPN_AUTH=", + "OPENVPN_PROCESS_USER=root", + "OPENVPN_CUSTOM_CONFIG=", + "WIREGUARD_PRIVATE_KEY=", + "WIREGUARD_PRESHARED_KEY=", + "WIREGUARD_PUBLIC_KEY=", + "WIREGUARD_ALLOWED_IPS=", + "WIREGUARD_ADDRESSES=", + "WIREGUARD_MTU=1400", + "WIREGUARD_IMPLEMENTATION=auto", + "SERVER_REGIONS=", + "SERVER_COUNTRIES=", + "SERVER_CITIES=", + "SERVER_HOSTNAMES=", + "ISP=", + "OWNED_ONLY=no", + "PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET=", + "VPN_PORT_FORWARDING=off", + "VPN_PORT_FORWARDING_PROVIDER=", + "VPN_PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port", + "OPENVPN_CERT=", + "OPENVPN_KEY=", + "OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt", + "OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey", + "OPENVPN_ENCRYPTED_KEY=", + "OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key", + "OPENVPN_KEY_PASSPHRASE=", + "OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase", + "SERVER_NUMBER=", + "SERVER_NAMES=", + "FREE_ONLY=", + "MULTIHOP_ONLY=", + "PREMIUM_ONLY=", + "FIREWALL=on", + "FIREWALL_VPN_INPUT_PORTS=", + "FIREWALL_INPUT_PORTS=", + "FIREWALL_OUTBOUND_SUBNETS=", + "FIREWALL_DEBUG=off", + "LOG_LEVEL=info", + "HEALTH_SERVER_ADDRESS=127.0.0.1:9999", + "HEALTH_TARGET_ADDRESS=cloudflare.com:443", + "HEALTH_SUCCESS_WAIT_DURATION=5s", + "HEALTH_VPN_DURATION_INITIAL=6s", + "HEALTH_VPN_DURATION_ADDITION=5s", + "DOT=on", + "DOT_PROVIDERS=cloudflare", + "DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112", + "DOT_VERBOSITY=1", + "DOT_VERBOSITY_DETAILS=0", + "DOT_VALIDATION_LOGLEVEL=0", + "DOT_CACHING=on", + "DOT_IPV6=off", + "BLOCK_MALICIOUS=on", + "BLOCK_SURVEILLANCE=off", + "BLOCK_ADS=off", + "UNBLOCK=", + "DNS_UPDATE_PERIOD=24h", + "DNS_ADDRESS=127.0.0.1", + "DNS_KEEP_NAMESERVER=off", + "HTTPPROXY=", + "HTTPPROXY_LOG=off", + "HTTPPROXY_LISTENING_ADDRESS=:8888", + "HTTPPROXY_STEALTH=off", + "HTTPPROXY_USER=", + "HTTPPROXY_PASSWORD=", + "HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user", + "HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password", + "SHADOWSOCKS=off", + "SHADOWSOCKS_LOG=off", + "SHADOWSOCKS_LISTENING_ADDRESS=:8388", + "SHADOWSOCKS_PASSWORD=", + "SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password", + "SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305", + "HTTP_CONTROL_SERVER_LOG=on", + "HTTP_CONTROL_SERVER_ADDRESS=:8000", + "UPDATER_PERIOD=0", + "UPDATER_MIN_RATIO=0.8", + "UPDATER_VPN_SERVICE_PROVIDERS=", + "PUBLICIP_FILE=/tmp/gluetun/ip", + "PUBLICIP_PERIOD=12h", + "PPROF_ENABLED=no", + "PPROF_BLOCK_PROFILE_RATE=0", + "PPROF_MUTEX_PROFILE_RATE=0", + "PPROF_HTTP_SERVER_ADDRESS=:6060", + "VERSION_INFORMATION=on", + "TZ=", + "PUID=", + "PGID=" + ], + "Cmd": null, + "Healthcheck": { + "Test": [ + "CMD-SHELL", + "/gluetun-entrypoint healthcheck" + ], + "Interval": 5000000000, + "Timeout": 5000000000, + "StartPeriod": 10000000000, + "Retries": 1 + }, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": [ + "/gluetun-entrypoint" + ], + "OnBuild": null, + "Labels": { + "org.opencontainers.image.authors": "quentin.mcgaw@gmail.com", + "org.opencontainers.image.created": "2023-07-22T16:07:05.641Z", + "org.opencontainers.image.description": "VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.", + "org.opencontainers.image.documentation": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.licenses": "MIT", + "org.opencontainers.image.revision": "eecfb3952f202c0de3867d88e96d80c6b0f48359", + "org.opencontainers.image.source": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.title": "gluetun", + "org.opencontainers.image.url": "https://github.com/qdm12/gluetun", + "org.opencontainers.image.version": "latest" + } + }, + "Architecture": "amd64", + "Os": "linux", + "Size": 42602255, + "VirtualSize": 42602255, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/a20c9490a23ee8af51898892d9bf32258d44e0e07f3799475be8e8f273a50f73/diff:/var/lib/docker/overlay2/d4c97f367c37c6ada9de57f438a3e19cc714be2a54a6f582a03de9e42d88b344/diff", + "MergedDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/merged", + "UpperDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/diff", + "WorkDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/work" + }, + "Name": "overlay2" + }, + "RootFS": { + "Type": "layers", + "Layers": [ + "sha256:78a822fe2a2d2c84f3de4a403188c45f623017d6a4521d23047c9fbb0801794c", + "sha256:122dbeefc08382d88b3fe57ad81c1e2428af5b81c172d112723a33e2a20fe880", + "sha256:3d215e55b88a99dcd7cf4349618326ab129771e12fdf6c6ef5cbb71a265dbb6c" + ] + }, + "Metadata": { + "LastTagTime": "0001-01-01T00:00:00Z" + } +} diff --git a/pkg/container/mocks/data/image02.json b/pkg/container/mocks/data/image_running.json similarity index 100% rename from pkg/container/mocks/data/image02.json rename to pkg/container/mocks/data/image_running.json diff --git a/scripts/contnet-tests.sh b/scripts/contnet-tests.sh new file mode 100755 index 0000000..25269dc --- /dev/null +++ b/scripts/contnet-tests.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -e + +function exit_env_err() { + >&2 echo "Required environment variable not set: $1" + exit 1 +} + +if [ -z "$VPN_SERVICE_PROVIDER" ]; then exit_env_err "VPN_SERVICE_PROVIDER"; fi +if [ -z "$OPENVPN_USER" ]; then exit_env_err "OPENVPN_USER"; fi +if [ -z "$OPENVPN_PASSWORD" ]; then exit_env_err "OPENVPN_PASSWORD"; fi +# if [ -z "$SERVER_COUNTRIES" ]; then exit_env_err "SERVER_COUNTRIES"; fi + + +export SERVER_COUNTRIES=${SERVER_COUNTRIES:"Sweden"} +REPO_ROOT="$(git rev-parse --show-toplevel)" +COMPOSE_FILE="$REPO_ROOT/dockerfiles/container-networking/docker-compose.yml" +DEFAULT_WATCHTOWER="$REPO_ROOT/watchtower" +WATCHTOWER="$*" +WATCHTOWER=${WATCHTOWER:-$DEFAULT_WATCHTOWER} +echo "repo root path is $REPO_ROOT" +echo "watchtower path is $WATCHTOWER" +echo "compose file path is $COMPOSE_FILE" + +echo; echo "=== Forcing network container producer update..." + +echo "Pull previous version of gluetun..." +docker pull qmcgaw/gluetun:v3.34.3 +echo "Fake new version of gluetun by retagging v3.34.4 as v3.35.0..." +docker tag qmcgaw/gluetun:v3.34.3 qmcgaw/gluetun:v3.35.0 + +echo; echo "=== Creating containers..." + +docker compose -p "wt-contnet" -f "$COMPOSE_FILE" up -d + +echo; echo "=== Running watchtower" +$WATCHTOWER --run-once + +echo; echo "=== Removing containers..." + +docker compose -p "wt-contnet" -f "$COMPOSE_FILE" down