From d6987855d1aae559fdcfbb32e15c0afacdb3c5ab Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Mon, 9 May 2022 17:52:04 -0300 Subject: [PATCH 1/2] feat: add network option --- docker.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++- options.go | 11 +++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/docker.go b/docker.go index aa358698..b6cc7efb 100644 --- a/docker.go +++ b/docker.go @@ -15,6 +15,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/orlangure/gnomock/internal/cleaner" @@ -30,6 +31,7 @@ const ( ) var duplicateContainerRegexp = regexp.MustCompile(duplicateContainerPattern) +var networkLock sync.Mutex type docker struct { client *client.Client @@ -304,7 +306,17 @@ func (d *docker) createContainer(ctx context.Context, image string, ports NamedP Mounts: mounts, } - resp, err := d.client.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, cfg.ContainerName) + var networkConfig *network.NetworkingConfig = nil + var err error + + if cfg.Network != "" { + networkConfig, err = d.getNetworkConfig(ctx, cfg.Network) + if err != nil { + return nil, err + } + } + + resp, err := d.client.ContainerCreate(ctx, containerConfig, hostConfig, networkConfig, nil, cfg.ContainerName) if err == nil { return &resp, nil } @@ -326,6 +338,61 @@ func (d *docker) createContainer(ctx context.Context, image string, ports NamedP return &resp, err } +func (d *docker) getNetworkConfig(ctx context.Context, name string) (*network.NetworkingConfig, error) { + networkLock.Lock() + defer networkLock.Unlock() + + networkID, err := d.getNetwork(ctx, name) + if err != nil { + return nil, err + } + + if networkID == "" { + networkID, err = d.createNetwork(ctx, name) + if err != nil { + return nil, err + } + } + + return &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + name: { + NetworkID: networkID, + }, + }, + }, nil +} + +func (d *docker) getNetwork(ctx context.Context, name string) (string, error) { + retrievedNetworks, err := d.client.NetworkList(ctx, types.NetworkListOptions{}) + + if err != nil { + return "", fmt.Errorf("could not get network: %w", err) + } + + existingNetworks := make([]types.NetworkResource, 0) + for _, network := range retrievedNetworks { + if network.Name == name { + existingNetworks = append(existingNetworks, network) + } + } + + if len(existingNetworks) > 0 { + return existingNetworks[0].ID, nil + } + + return "", nil +} + +func (d *docker) createNetwork(ctx context.Context, name string) (string, error) { + response, err := d.client.NetworkCreate(ctx, name, types.NetworkCreate{}) + if err != nil { + return "", fmt.Errorf("can't create network: %w", err) + } + + return response.ID, nil +} + func (d *docker) boundNamedPorts(json types.ContainerJSON, namedPorts NamedPorts) (NamedPorts, error) { boundNamedPorts := make(NamedPorts) diff --git a/options.go b/options.go index a8c13a79..dd045ff3 100644 --- a/options.go +++ b/options.go @@ -96,6 +96,12 @@ func WithContainerName(name string) Option { } } +func WithNetwork(name string) Option { + return func(o *Options) { + o.Network = name + } +} + // WithPrivileged starts a container in privileged mode (like `docker run // --privileged`). This option should not be used unless you really need it. // One use case for this option would be to run a Preset that has some kind of @@ -269,6 +275,11 @@ type Options struct { // {"username":"foo","password":"bar"} Auth string `json:"auth"` + // Network allows to specify the name of the network which the container + // will run on. If the specified network doesn't exist, it will create it. + // Otherwise, it will use the existing network. + Network string `json:"network"` + ctx context.Context init InitFunc healthcheck HealthcheckFunc From c4d47cd1e514a0fb0d60202e87012202af4d30d9 Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Mon, 9 May 2022 21:17:11 -0300 Subject: [PATCH 2/2] add test to cover network option --- gnomock_test.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/gnomock_test.go b/gnomock_test.go index 87aee9cc..5e5c4019 100644 --- a/gnomock_test.go +++ b/gnomock_test.go @@ -192,7 +192,7 @@ func TestGnomock_withCommand(t *testing.T) { } // See https://github.com/orlangure/gnomock/issues/302 -func TestGnomock_witUseLocalImagesFirst(t *testing.T) { +func TestGnomock_withUseLocalImagesFirst(t *testing.T) { t.Parallel() const ( @@ -228,6 +228,38 @@ func TestGnomock_witUseLocalImagesFirst(t *testing.T) { require.NoError(t, gnomock.Stop(container)) } +func TestGnomock_withNetwork(t *testing.T) { + t.Parallel() + + namedPorts := gnomock.NamedPorts{ + "web80": gnomock.TCP(testutil.GoodPort80), + "web8080": gnomock.TCP(testutil.GoodPort8080), + } + container, err := gnomock.StartCustom( + testutil.TestImage, namedPorts, + gnomock.WithHealthCheckInterval(time.Microsecond*500), + gnomock.WithHealthCheck(testutil.Healthcheck), + gnomock.WithInit(initf), + gnomock.WithContext(context.Background()), + gnomock.WithTimeout(time.Minute), + gnomock.WithEnv("GNOMOCK_TEST_1=foo"), + gnomock.WithEnv("GNOMOCK_TEST_2=bar"), + gnomock.WithNetwork("gnomock_network"), + gnomock.WithRegistryAuth(""), + ) + + require.NoError(t, err) + require.NotNil(t, container) + + addr := fmt.Sprintf("http://%s/", container.Address("web80")) + requireResponse(t, addr, "80") + + addr = fmt.Sprintf("http://%s/", container.Address("web8080")) + requireResponse(t, addr, "8080") + + require.NoError(t, gnomock.Stop(container)) +} + func initf(context.Context, *gnomock.Container) error { return nil }