From 71a4012a36a9bcd75c386aa17be86da107c061b1 Mon Sep 17 00:00:00 2001 From: Jake Sylvestre Date: Sun, 14 Mar 2021 18:27:15 -0400 Subject: [PATCH] Add networking (#10) * add networking to containers to support non-legacy link networking * gofmt --- container_test.go | 27 +++++++++++++++---------- go.mod | 1 + go.sum | 4 ++++ network.go | 24 +++++++++++++++++++++++ teardown.go | 50 ++++++++++++++++++++++++++++++++--------------- util.go | 27 +++++++++++++++++++++++++ volume.go | 4 +--- 7 files changed, 108 insertions(+), 29 deletions(-) create mode 100644 network.go create mode 100644 util.go diff --git a/container_test.go b/container_test.go index cf285bd..0cd3a24 100644 --- a/container_test.go +++ b/container_test.go @@ -14,11 +14,13 @@ type TestClient struct { Client } -func (c TestClient) CreateTestContainer() (id string, err error) { +func (c TestClient) CreateTestContainer(network string) (id string, err error) { created, err := c.CreateContainer(&container.Config{ Image: "docker.io/library/alpine", Tty: false, - }, nil, nil, nil, "") + }, &container.HostConfig{ + NetworkMode: container.NetworkMode(network), + }, nil, nil, "") if err != nil { return id, err @@ -33,22 +35,27 @@ func (c TestClient) CreateTestContainer() (id string, err error) { } func TestContainerLifecycle(t *testing.T) { - output := gofakeit.Word() client := TestClient{NewDockerClient()} - defer func() { - Nil(t, client.TeardownSession()) - }() - id, err := client.CreateTestContainer() + // we run this twice to ensure teardown occured correctly + containerNetworkName := gofakeit.Word() + containerOutput := gofakeit.Word() + + networkID, err := client.CreateNetwork(containerNetworkName) + Nil(t, err) + + id, err := client.CreateTestContainer(networkID) Nil(t, err) - res, err := client.Exec(id, []string{"echo", output}) + res, err := client.Exec(id, []string{"echo", containerOutput}) Nil(t, err) - if res.ExitCode != 0 && res.StdErr != "" && res.StdOut != output { - t.Errorf("expeceted output to equal %s", "hi") + if res.ExitCode != 0 && res.StdErr != "" && res.StdOut != containerOutput { + t.Errorf("expeceted containerOutput to equal %s", "hi") } // TODO verify _, err = client.ContainerStatus(id) Nil(t, err) + + Nil(t, client.TeardownSession()) } diff --git a/go.mod b/go.mod index f21c3de..4d95ad1 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/google/go-cmp v0.5.4 // indirect github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect diff --git a/go.sum b/go.sum index 6eba06f..0a08d19 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,10 @@ github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/network.go b/network.go new file mode 100644 index 0000000..cd48d99 --- /dev/null +++ b/network.go @@ -0,0 +1,24 @@ +package docker + +import "github.com/docker/docker/api/types" + +// CreateNetwork creates a docker network that can +// be used for communication in between containers +// see: https://docs.docker.com/network/ for details +func (c Client) CreateNetwork(name string) (networkID string, err error) { + networkResponse, err := c.NetworkCreate(c.Ctx, name, + types.NetworkCreate{ + CheckDuplicate: true, + Attachable: true, + Labels: c.getSessionLabels(), + }) + if err != nil { + return "", err + } + return networkResponse.ID, err +} + +// RemoveNetwork removes a network by id +func (c Client) RemoveNetwork(networkID string) error { + return c.NetworkRemove(c.Ctx, networkID) +} diff --git a/teardown.go b/teardown.go index 719defb..9e5701f 100644 --- a/teardown.go +++ b/teardown.go @@ -1,29 +1,32 @@ package docker import ( - "fmt" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" + "github.com/hashicorp/go-multierror" ) // TeardownSession removes all resources created during the session func (c *Client) TeardownSession() (err error) { err = c.TeardownSessionContainers() if err != nil { - return err + err = multierror.Append(err) + } + err = c.TeardownSessionVolumes() + if err != nil { + err = multierror.Append(err) + } + err = c.TeardownSessionNetworks() + if err != nil { + err = multierror.Append(err) } - return c.TeardownSessionVolumes() + return err } // TeardownSessionVolumes removes containers created in the current session using RemoveVolume // the current session is determined based on the Client so this method should be called // once per Client -func (c *Client) TeardownSessionVolumes() error { - volumes, err := c.VolumeList(c.Ctx, filters.NewArgs(filters.KeyValuePair{ - Key: "label", - Value: fmt.Sprintf("sessionId=%s", c.SessionID), - })) +func (c *Client) TeardownSessionVolumes() (err error) { + volumes, err := c.VolumeList(c.Ctx, filterByLabels(c.getSessionLabels())) if err != nil { return err @@ -39,16 +42,31 @@ func (c *Client) TeardownSessionVolumes() error { return err } +// TeardownSessionNetworks removes containers created in the current session using StopContainer +// and RemoveContainer the current session is determined based on the Client +// so this method should be called once per Client +func (c *Client) TeardownSessionNetworks() (err error) { + networks, err := c.NetworkList(c.Ctx, types.NetworkListOptions{Filters: filterByLabels(c.getSessionLabels())}) + if err != nil { + return err + } + + for _, network := range networks { + err = c.RemoveNetwork(network.ID) + if err != nil { + return err + } + } + return err +} + // TeardownSessionContainers removes containers created in the current session using StopContainer // and RemoveContainer the current session is determined based on the Client // so this method should be called once per Client -func (c *Client) TeardownSessionContainers() error { +func (c *Client) TeardownSessionContainers() (err error) { filter := types.ContainerListOptions{ - All: true, - Filters: filters.NewArgs(filters.KeyValuePair{ - Key: "label", - Value: fmt.Sprintf("sessionId=%s", c.SessionID), - }), + All: true, + Filters: filterByLabels(c.getSessionLabels()), } containers, err := c.ContainerList(c.Ctx, filter) diff --git a/util.go b/util.go new file mode 100644 index 0000000..0b06599 --- /dev/null +++ b/util.go @@ -0,0 +1,27 @@ +package docker + +import ( + "fmt" + + "github.com/docker/docker/api/types/filters" +) + +// getSessionLabels gets labels set for all resources created in this session +func (c Client) getSessionLabels() map[string]string { + return map[string]string{ + "sessionId": c.SessionID, + "created-by": "docker-utils", + } +} + +// filterByLabels creates filters.Args based on a set of labels +func filterByLabels(labels map[string]string) (args filters.Args) { + var kvps []filters.KeyValuePair + for key, value := range labels { + kvps = append(kvps, filters.KeyValuePair{ + Key: "label", + Value: fmt.Sprintf("%s=%s", key, value), + }) + } + return filters.NewArgs(kvps...) +} diff --git a/volume.go b/volume.go index 3556350..fb08b09 100644 --- a/volume.go +++ b/volume.go @@ -55,9 +55,7 @@ func (c *Client) CreateVolume(name string) (err error) { vol, err := c.VolumeCreate(c.Ctx, volume.VolumeCreateBody{ Driver: Driver, Name: name, - Labels: map[string]string{ - "sessionId": c.SessionID, - }, + Labels: c.getSessionLabels(), }) if err != nil { return err