From a91fb6816fdd0db3b2ab9d944b42e49eaa44d4a2 Mon Sep 17 00:00:00 2001 From: Timofey Date: Thu, 15 Apr 2021 00:28:57 +0300 Subject: [PATCH] Invoke Docker API directly * Remove third-party docker client dependency * Simplify code and tests Might need to run `go mod tidy` and `go mod vendor` afterwards --- app/discovery/provider/docker.go | 205 ++++++++++-------- app/discovery/provider/docker_client_mock.go | 70 +----- app/discovery/provider/docker_test.go | 175 ++++----------- .../provider/testdata/containers.json | 91 ++++++++ app/main.go | 9 +- 5 files changed, 260 insertions(+), 290 deletions(-) create mode 100644 app/discovery/provider/testdata/containers.json diff --git a/app/discovery/provider/docker.go b/app/discovery/provider/docker.go index 1b5c623..e55486b 100644 --- a/app/discovery/provider/docker.go +++ b/app/discovery/provider/docker.go @@ -2,16 +2,18 @@ package provider import ( "context" - "errors" + "encoding/json" "fmt" + "net" + "net/http" "regexp" "sort" "strconv" "strings" "time" - dc "github.com/fsouza/go-dockerclient" log "github.com/go-pkgz/lgr" + "github.com/pkg/errors" "github.com/umputun/reproxy/app/discovery" ) @@ -27,41 +29,29 @@ import ( type Docker struct { DockerClient DockerClient Excludes []string - Network string AutoAPI bool } // DockerClient defines interface listing containers and subscribing to events type DockerClient interface { - ListContainers(opts dc.ListContainersOptions) ([]dc.APIContainers, error) - AddEventListenerWithOptions(options dc.EventsOptions, listener chan<- *dc.APIEvents) error + ListContainers() ([]containerInfo, error) } -// containerInfo is simplified docker.APIEvents for containers only +// containerInfo is simplified view of container metadata type containerInfo struct { - ID string - Name string - TS time.Time - Labels map[string]string - IP string - Ports []int + ID string `json:"Id"` + Name string `json:"-"` + State string `json:"State"` + Labels map[string]string `json:"Labels"` + TS time.Time `json:"-"` + IP string `json:"-"` + Ports []int `json:"-"` } // Events gets eventsCh with all containers-related docker events events func (d *Docker) Events(ctx context.Context) (res <-chan discovery.ProviderID) { eventsCh := make(chan discovery.ProviderID) - go func() { - defer close(eventsCh) - // loop over to recover from failed events call - for { - err := d.events(ctx, d.DockerClient, eventsCh) // publish events to eventsCh in a blocking loop - if err == context.Canceled || err == context.DeadlineExceeded { - return - } - log.Printf("[WARN] docker events listener failed (restarted), %v", err) - time.Sleep(1 * time.Second) // prevent busy loop on restart of event listener - } - }() + go d.events(ctx, eventsCh) return eventsCh } @@ -133,7 +123,7 @@ func (d *Docker) List() ([]discovery.URLMapper, error) { srcRegex, err := regexp.Compile(srcURL) if err != nil { - return nil, fmt.Errorf("invalid src regex %s: %w", srcURL, err) + return nil, errors.Wrapf(err, "invalid src regex %s", srcURL) } // docker server label may have multiple, comma separated servers @@ -165,7 +155,7 @@ func (d *Docker) matchedPort(c containerInfo) (port int, err error) { if v, ok := c.Labels["reproxy.port"]; ok { rp, err := strconv.Atoi(v) if err != nil { - return 0, fmt.Errorf("invalid reproxy.port %s: %w", v, err) + return 0, errors.Wrapf(err, "invalid reproxy.port %s", v) } for _, p := range c.Ports { // set port to reproxy.port if matched with one of exposed @@ -178,105 +168,140 @@ func (d *Docker) matchedPort(c containerInfo) (port int, err error) { return port, nil } -// activate starts blocking listener for all docker events -// filters everything except "container" type, detects stop/start events and publishes signals to eventsCh -func (d *Docker) events(ctx context.Context, client DockerClient, eventsCh chan discovery.ProviderID) error { - dockerEventsCh := make(chan *dc.APIEvents) - err := client.AddEventListenerWithOptions(dc.EventsOptions{ - Filters: map[string][]string{"type": {"container"}, "event": {"start", "die", "destroy", "restart", "pause"}}}, - dockerEventsCh) - if err != nil { - return fmt.Errorf("can't add even listener: %w", err) - } +// changed in tests +var dockerPollingInterval = time.Second * 10 + +// events starts monitoring changes in running containers and sends refresh +// notification to eventsCh when change(s) are detected. Blocks caller +func (d *Docker) events(ctx context.Context, eventsCh chan<- discovery.ProviderID) error { + ticker := time.NewTicker(dockerPollingInterval) + defer ticker.Stop() - eventsCh <- discovery.PIDocker // initial emmit for { select { case <-ctx.Done(): return ctx.Err() - case ev, ok := <-dockerEventsCh: - if !ok { - return errors.New("events closed") - } - log.Printf("[DEBUG] api event %+v", ev) - containerName := strings.TrimPrefix(ev.Actor.Attributes["name"], "/") - - if discovery.Contains(containerName, d.Excludes) { - log.Printf("[DEBUG] container %s excluded", containerName) - continue - } - log.Printf("[INFO] new docker event: container %s, status %s", containerName, ev.Status) + case <-ticker.C: eventsCh <- discovery.PIDocker } } } func (d *Docker) listContainers() (res []containerInfo, err error) { - - portsExposed := func(c dc.APIContainers) (ports []int) { - if len(c.Ports) == 0 { - return nil - } - for _, p := range c.Ports { - ports = append(ports, int(p.PrivatePort)) - } - return ports - } - - containers, err := d.DockerClient.ListContainers(dc.ListContainersOptions{All: false}) + containers, err := d.DockerClient.ListContainers() if err != nil { - return nil, fmt.Errorf("can't list containers: %w", err) + return nil, errors.Wrap(err, "can't list containers") } + log.Printf("[DEBUG] total containers = %d", len(containers)) for _, c := range containers { if c.State != "running" { - log.Printf("[DEBUG] skip container %s due to state %s", c.Names[0], c.State) + log.Printf("[DEBUG] skip container %s due to state %s", c.Name, c.State) continue } - containerName := strings.TrimPrefix(c.Names[0], "/") - if discovery.Contains(containerName, d.Excludes) || strings.EqualFold(containerName, "reproxy") { - log.Printf("[DEBUG] container %s excluded", containerName) + + if discovery.Contains(c.Name, d.Excludes) || strings.EqualFold(c.Name, "reproxy") { + log.Printf("[DEBUG] container %s excluded", c.Name) continue } if v, ok := c.Labels["reproxy.enabled"]; ok { if strings.EqualFold(v, "false") || strings.EqualFold(v, "no") || v == "0" { - log.Printf("[DEBUG] skip container %s due to reproxy.enabled=%s", containerName, v) + log.Printf("[DEBUG] skip container %s due to reproxy.enabled=%s", c.Name, v) continue } } - var ip string - for k, v := range c.Networks.Networks { - if d.Network == "" || k == d.Network { // match on network name if defined - ip = v.IPAddress - break - } - } - if ip == "" { - log.Printf("[DEBUG] skip container %s, no ip on %+v", c.Names[0], c.Networks.Networks) + if c.IP == "" { + log.Printf("[DEBUG] skip container %s, no ip on defined networks", c.Name) continue } - ports := portsExposed(c) - if len(ports) == 0 { - log.Printf("[DEBUG] skip container %s, no exposed ports", c.Names[0]) + if len(c.Ports) == 0 { + log.Printf("[DEBUG] skip container %s, no exposed ports", c.Name) continue } - ci := containerInfo{ - Name: containerName, - ID: c.ID, - TS: time.Unix(c.Created/1000, 0), - Labels: c.Labels, - IP: ip, - Ports: ports, - } - - log.Printf("[DEBUG] running container added, %+v", ci) - res = append(res, ci) + log.Printf("[DEBUG] running container added, %+v", c) + res = append(res, c) } log.Print("[DEBUG] completed list") return res, nil } + +type dockerClient struct { + client http.Client + network string // network for IP selection +} + +// NewDockerClient constructs docker client for given host and network +func NewDockerClient(host, network string) DockerClient { + var schemaRegex = regexp.MustCompile("^(?:([a-z0-9]+)://)?(.*)$") + parts := schemaRegex.FindStringSubmatch(host) + proto, addr := parts[1], parts[2] + log.Printf("[DEBUG] Configuring docker client to talk to %s via %s", addr, proto) + + client := http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial(proto, addr) + }, + }, + Timeout: time.Second * 5, + } + + return &dockerClient{client, network} +} + +func (d *dockerClient) ListContainers() ([]containerInfo, error) { + + const dockerAPIVersion = "v1.41" + + resp, err := d.client.Get(fmt.Sprintf("http://localhost/%s/containers/json", dockerAPIVersion)) + if err != nil { + return nil, errors.Wrap(err, "failed connection to docker socket") + } + + defer resp.Body.Close() + + response := []struct { + containerInfo + Created int64 + NetworkSettings struct { + Networks map[string]struct { + IPAddress string + } + } + Names []string + ExposedPorts []struct{ PrivatePort int } `json:"Ports"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, errors.Wrap(err, "failed to parse response from docker daemon") + } + + containers := make([]containerInfo, len(response)) + + for i, c := range response { + // fill remaining fields + c.TS = time.Unix(c.Created, 0) + + for k, v := range c.NetworkSettings.Networks { + if d.network == "" || k == d.network { // match on network name if defined + c.IP = v.IPAddress + break + } + } + + c.Name = strings.TrimPrefix(c.Names[0], "/") + + for _, p := range c.ExposedPorts { + c.Ports = append(c.Ports, p.PrivatePort) + } + + containers[i] = c.containerInfo + } + + return containers, nil +} diff --git a/app/discovery/provider/docker_client_mock.go b/app/discovery/provider/docker_client_mock.go index dadd9ce..7b0e88d 100644 --- a/app/discovery/provider/docker_client_mock.go +++ b/app/discovery/provider/docker_client_mock.go @@ -5,8 +5,6 @@ package provider import ( "sync" - - dclient "github.com/fsouza/go-dockerclient" ) // DockerClientMock is a mock implementation of DockerClient. @@ -15,10 +13,7 @@ import ( // // // make and configure a mocked DockerClient // mockedDockerClient := &DockerClientMock{ -// AddEventListenerWithOptionsFunc: func(options dclient.EventsOptions, listener chan<- *dclient.APIEvents) error { -// panic("mock out the AddEventListenerWithOptions method") -// }, -// ListContainersFunc: func(opts dclient.ListContainersOptions) ([]dclient.APIContainers, error) { +// ListContainersFunc: func() ([]containerInfo, error) { // panic("mock out the ListContainers method") // }, // } @@ -28,90 +23,37 @@ import ( // // } type DockerClientMock struct { - // AddEventListenerWithOptionsFunc mocks the AddEventListenerWithOptions method. - AddEventListenerWithOptionsFunc func(options dclient.EventsOptions, listener chan<- *dclient.APIEvents) error - // ListContainersFunc mocks the ListContainers method. - ListContainersFunc func(opts dclient.ListContainersOptions) ([]dclient.APIContainers, error) + ListContainersFunc func() ([]containerInfo, error) // calls tracks calls to the methods. calls struct { - // AddEventListenerWithOptions holds details about calls to the AddEventListenerWithOptions method. - AddEventListenerWithOptions []struct { - // Options is the options argument value. - Options dclient.EventsOptions - // Listener is the listener argument value. - Listener chan<- *dclient.APIEvents - } // ListContainers holds details about calls to the ListContainers method. ListContainers []struct { - // Opts is the opts argument value. - Opts dclient.ListContainersOptions } } - lockAddEventListenerWithOptions sync.RWMutex - lockListContainers sync.RWMutex -} - -// AddEventListenerWithOptions calls AddEventListenerWithOptionsFunc. -func (mock *DockerClientMock) AddEventListenerWithOptions(options dclient.EventsOptions, listener chan<- *dclient.APIEvents) error { - if mock.AddEventListenerWithOptionsFunc == nil { - panic("DockerClientMock.AddEventListenerWithOptionsFunc: method is nil but DockerClient.AddEventListenerWithOptions was just called") - } - callInfo := struct { - Options dclient.EventsOptions - Listener chan<- *dclient.APIEvents - }{ - Options: options, - Listener: listener, - } - mock.lockAddEventListenerWithOptions.Lock() - mock.calls.AddEventListenerWithOptions = append(mock.calls.AddEventListenerWithOptions, callInfo) - mock.lockAddEventListenerWithOptions.Unlock() - return mock.AddEventListenerWithOptionsFunc(options, listener) -} - -// AddEventListenerWithOptionsCalls gets all the calls that were made to AddEventListenerWithOptions. -// Check the length with: -// len(mockedDockerClient.AddEventListenerWithOptionsCalls()) -func (mock *DockerClientMock) AddEventListenerWithOptionsCalls() []struct { - Options dclient.EventsOptions - Listener chan<- *dclient.APIEvents -} { - var calls []struct { - Options dclient.EventsOptions - Listener chan<- *dclient.APIEvents - } - mock.lockAddEventListenerWithOptions.RLock() - calls = mock.calls.AddEventListenerWithOptions - mock.lockAddEventListenerWithOptions.RUnlock() - return calls + lockListContainers sync.RWMutex } // ListContainers calls ListContainersFunc. -func (mock *DockerClientMock) ListContainers(opts dclient.ListContainersOptions) ([]dclient.APIContainers, error) { +func (mock *DockerClientMock) ListContainers() ([]containerInfo, error) { if mock.ListContainersFunc == nil { panic("DockerClientMock.ListContainersFunc: method is nil but DockerClient.ListContainers was just called") } callInfo := struct { - Opts dclient.ListContainersOptions - }{ - Opts: opts, - } + }{} mock.lockListContainers.Lock() mock.calls.ListContainers = append(mock.calls.ListContainers, callInfo) mock.lockListContainers.Unlock() - return mock.ListContainersFunc(opts) + return mock.ListContainersFunc() } // ListContainersCalls gets all the calls that were made to ListContainers. // Check the length with: // len(mockedDockerClient.ListContainersCalls()) func (mock *DockerClientMock) ListContainersCalls() []struct { - Opts dclient.ListContainersOptions } { var calls []struct { - Opts dclient.ListContainersOptions } mock.lockListContainers.RLock() calls = mock.calls.ListContainers diff --git a/app/discovery/provider/docker_test.go b/app/discovery/provider/docker_test.go index f284cf7..f7dbf38 100644 --- a/app/discovery/provider/docker_test.go +++ b/app/discovery/provider/docker_test.go @@ -1,71 +1,51 @@ package provider import ( - "context" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" "testing" "time" - dc "github.com/fsouza/go-dockerclient" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDocker_List(t *testing.T) { dclient := &DockerClientMock{ - ListContainersFunc: func(opts dc.ListContainersOptions) ([]dc.APIContainers, error) { - return []dc.APIContainers{ - {Names: []string{"c0"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.2"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12348}, - }, + ListContainersFunc: func() ([]containerInfo, error) { + return []containerInfo{ + { + Name: "c0", State: "running", IP: "127.0.0.2", Ports: []int{12348}, Labels: map[string]string{"reproxy.route": "^/a/(.*)", "reproxy.dest": "/a/$1", "reproxy.server": "example.com", "reproxy.ping": "/ping"}, }, - {Names: []string{"c1"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.2"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12345}, - }, + { + Name: "c1", State: "running", IP: "127.0.0.2", Ports: []int{12345}, Labels: map[string]string{"reproxy.route": "^/api/123/(.*)", "reproxy.dest": "/blah/$1", "reproxy.server": "example.com", "reproxy.ping": "/ping"}, }, - {Names: []string{"c2"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.3"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12346}, - }, + { + Name: "c2", State: "running", IP: "127.0.0.3", Ports: []int{12346}, Labels: map[string]string{"reproxy.enabled": "y"}, }, - {Names: []string{"c3"}, State: "stopped"}, - {Names: []string{"c4"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"other": {IPAddress: "127.0.0.2"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12345}, - }, + { + Name: "c3", State: "stopped", }, - {Names: []string{"c5"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.122"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 2345}, - }, + { + Name: "c4", State: "running", IP: "127.0.0.2", Ports: []int{12345}, + }, + { + Name: "c5", State: "running", IP: "127.0.0.122", Ports: []int{2345}, Labels: map[string]string{"reproxy.enabled": "false"}, }, }, nil }, } - d := Docker{DockerClient: dclient, Network: "bridge"} + d := Docker{DockerClient: dclient} res, err := d.List() require.NoError(t, err) require.Equal(t, 3, len(res)) @@ -86,97 +66,32 @@ func TestDocker_List(t *testing.T) { assert.Equal(t, "*", res[2].Server) } -func TestDocker_ListWithAutoAPI(t *testing.T) { - dclient := &DockerClientMock{ - ListContainersFunc: func(opts dc.ListContainersOptions) ([]dc.APIContainers, error) { - return []dc.APIContainers{ - {Names: []string{"c1"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.2"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 1345}, - {PrivatePort: 12345}, - }, - Labels: map[string]string{"reproxy.route": "^/api/123/(.*)", "reproxy.dest": "/blah/$1", - "reproxy.port": "12345", "reproxy.server": "example.com, example2.com", "reproxy.ping": "/ping"}, - }, - {Names: []string{"c2"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.3"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12346}, - }, - }, - {Names: []string{"c3"}, State: "stopped"}, - {Names: []string{"c4"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"other": {IPAddress: "127.0.0.2"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 12345}, - }, - }, - {Names: []string{"c5"}, State: "running", - Networks: dc.NetworkList{ - Networks: map[string]dc.ContainerNetwork{"bridge": {IPAddress: "127.0.0.122"}}, - }, - Ports: []dc.APIPort{ - {PrivatePort: 2345}, - }, - Labels: map[string]string{"reproxy.enabled": "false"}, - }, - }, nil - }, - } +func TestDockerClient(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1.41/containers/json", r.URL.Path) - d := Docker{DockerClient: dclient, Network: "bridge", AutoAPI: true} - res, err := d.List() - require.NoError(t, err) - require.Equal(t, 3, len(res)) + // obtained using curl --unix-socket /var/run/docker.sock http://localhost/v1.41/containers/json + resp, err := ioutil.ReadFile("testdata/containers.json") + require.NoError(t, err) + w.Write(resp) + })) - assert.Equal(t, "^/api/123/(.*)", res[0].SrcMatch.String()) - assert.Equal(t, "http://127.0.0.2:12345/blah/$1", res[0].Dst) - assert.Equal(t, "example.com", res[0].Server) - assert.Equal(t, "http://127.0.0.2:12345/ping", res[0].PingURL) + defer srv.Close() + addr := fmt.Sprintf("tcp://%s", strings.TrimPrefix(srv.URL, "http://")) - assert.Equal(t, "^/api/123/(.*)", res[1].SrcMatch.String()) - assert.Equal(t, "http://127.0.0.2:12345/blah/$1", res[1].Dst) - assert.Equal(t, "example2.com", res[1].Server) - assert.Equal(t, "http://127.0.0.2:12345/ping", res[1].PingURL) + client := NewDockerClient(addr, "bridge") + c, err := client.ListContainers() + require.NoError(t, err, "unexpected error while listing containers") - assert.Equal(t, "^/api/c2/(.*)", res[2].SrcMatch.String()) - assert.Equal(t, "http://127.0.0.3:12346/$1", res[2].Dst) - assert.Equal(t, "http://127.0.0.3:12346/ping", res[2].PingURL) - assert.Equal(t, "*", res[2].Server) -} - -func TestDocker_Events(t *testing.T) { - dclient := &DockerClientMock{ - AddEventListenerWithOptionsFunc: func(options dc.EventsOptions, listener chan<- *dc.APIEvents) error { - go func() { - time.Sleep(30 * time.Millisecond) - listener <- &dc.APIEvents{Type: "container", Status: "start", - Actor: dc.APIActor{Attributes: map[string]string{"name": "/c1"}}} - time.Sleep(30 * time.Millisecond) - listener <- &dc.APIEvents{Type: "container", Status: "start", - Actor: dc.APIActor{Attributes: map[string]string{"name": "/c2"}}} - }() - return nil - }, - } - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - d := Docker{DockerClient: dclient} - ch := d.Events(ctx) - - events := 0 - for range ch { - t.Log("event") - events++ - } - assert.Equal(t, 2+1, events, "initial event plus 2 more") + assert.Len(t, c, 2) + + assert.NotEmpty(t, c[0].ID) + assert.Equal(t, "nginx", c[0].Name) + assert.Equal(t, "running", c[0].State) + assert.Equal(t, "172.17.0.3", c[0].IP) + assert.Equal(t, "y", c[0].Labels["reproxy.enabled"]) + assert.Equal(t, []int{80}, c[0].Ports) + assert.Equal(t, time.Unix(1618417435, 0), c[0].TS) + + assert.Equal(t, "", c[1].IP) } diff --git a/app/discovery/provider/testdata/containers.json b/app/discovery/provider/testdata/containers.json new file mode 100644 index 0000000..1191277 --- /dev/null +++ b/app/discovery/provider/testdata/containers.json @@ -0,0 +1,91 @@ +[ + { + "Id": "6f3c99ca4d83811ae1bf1c3e288ac229288028a8b097565cb93eed85ccc4c9b4", + "Names": [ + "/nginx" + ], + "Image": "nginx", + "ImageID": "sha256:62d49f9bab67f7c70ac3395855bf01389eb3175b374e621f6f191bf31b54cd5b", + "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'", + "Created": 1618417435, + "Ports": [ + { + "PrivatePort": 80, + "Type": "tcp" + } + ], + "Labels": { + "maintainer": "NGINX Docker Maintainers ", + "reproxy.enabled": "y" + }, + "State": "running", + "Status": "Up 2 seconds", + "HostConfig": { + "NetworkMode": "default" + }, + "NetworkSettings": { + "Networks": { + "bridge": { + "IPAMConfig": null, + "Links": null, + "Aliases": null, + "NetworkID": "7cf54d5b5b0cea56bcd4b5a8d7fd00ec6da9283a77e3cc0bd1252ebab60eca67", + "EndpointID": "481acb2624a95a12826357b1d6e9560ff0225b102ef932bb4dc484770f5b632b", + "Gateway": "172.17.0.1", + "IPAddress": "172.17.0.3", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "02:42:42:42:42:03", + "DriverOpts": null + } + } + }, + "Mounts": [] + }, + { + "Id": "d5503b81f356718bb01357daab63acb88eaae682bcb638383fadbac66e0bb830", + "Names": [ + "/weather" + ], + "Image": "weather:latest", + "ImageID": "sha256:28ceada083a985a36f794904261b0a82d2c3be9de57076acab90ac304f77952e", + "Command": "/usr/local/bin/weather", + "Created": 1618410445, + "Ports": [ + { + "PrivatePort": 8000, + "Type": "tcp" + } + ], + "Labels": { + "hello": "world" + }, + "State": "running", + "Status": "Up 2 hours (healthy)", + "HostConfig": { + "NetworkMode": "default" + }, + "NetworkSettings": { + "Networks": { + "someOtherNetwork": { + "IPAMConfig": null, + "Links": null, + "Aliases": null, + "NetworkID": "7cf54d5b5b0cea56bcd4b5a8d7fd00ec6da9283a77e3cc0bd1252ebab60eca67", + "EndpointID": "b4a14144a7300b37d4e907fdffa68b5234f67d2e7bb5db869198cf2db78d6280", + "Gateway": "172.17.0.1", + "IPAddress": "172.17.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "02:42:42:42:42:02", + "DriverOpts": null + } + } + }, + "Mounts": [] + } +] \ No newline at end of file diff --git a/app/main.go b/app/main.go index 39484d0..763eebb 100644 --- a/app/main.go +++ b/app/main.go @@ -13,7 +13,6 @@ import ( "syscall" "time" - docker "github.com/fsouza/go-dockerclient" log "github.com/go-pkgz/lgr" "github.com/umputun/go-flags" "gopkg.in/natefinch/lumberjack.v2" @@ -203,15 +202,13 @@ func makeProviders() ([]discovery.Provider, error) { } if opts.Docker.Enabled { - client, err := docker.NewClient(opts.Docker.Host) - if err != nil { - return nil, fmt.Errorf("failed to make docker client %w", err) - } + client := provider.NewDockerClient(opts.Docker.Host, opts.Docker.Network) + if opts.Docker.AutoAPI { log.Printf("[INFO] auto-api enabled for docker") } res = append(res, &provider.Docker{DockerClient: client, Excludes: opts.Docker.Excluded, - Network: opts.Docker.Network, AutoAPI: opts.Docker.AutoAPI}) + AutoAPI: opts.Docker.AutoAPI}) } if len(res) == 0 && opts.Assets.Location == "" {