Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: network option #534

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 68 additions & 1 deletion docker.go
Expand Up @@ -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"
Expand All @@ -30,6 +31,7 @@ const (
)

var duplicateContainerRegexp = regexp.MustCompile(duplicateContainerPattern)
var networkLock sync.Mutex

type docker struct {
client *client.Client
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since only the first ID is returned, I suggest to drop the slice, and just return whatever ID is found on line 376 instead of maintaining a list of existingNetwork.

}

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)

Expand Down
34 changes: 33 additions & 1 deletion gnomock_test.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}
Expand Down
11 changes: 11 additions & 0 deletions options.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down