From 940e04371cb2211d83f2891640d323465382d134 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Mon, 8 Jun 2020 22:16:07 +0200 Subject: [PATCH] feat: dockerutils lib to start container images --- pkg/testutils/dockerutils/builder.go | 122 ++++--- pkg/testutils/dockerutils/client.go | 205 ++++++++++- pkg/testutils/dockerutils/client_test.go | 443 +++++++++++++++++++++++ 3 files changed, 713 insertions(+), 57 deletions(-) create mode 100644 pkg/testutils/dockerutils/client_test.go diff --git a/pkg/testutils/dockerutils/builder.go b/pkg/testutils/dockerutils/builder.go index 6bf5d86..ba5edcd 100644 --- a/pkg/testutils/dockerutils/builder.go +++ b/pkg/testutils/dockerutils/builder.go @@ -71,6 +71,12 @@ func (builder *Builder) Mounts(mounts map[string]string) *Builder { return builder } +// Network add the container with aliasses to a specific network +func (builder *Builder) Network(networkName string, aliasses ...string) *Builder { + builder.networks[networkName] = aliasses + return builder +} + // Port defines a port forwarding from the host machine to the container // Examples: // - 8080:8080 @@ -109,92 +115,118 @@ func (builder *Builder) Start(ctx context.Context) (string, error) { } } - // check if ports are available + // Network: portbinding Host->Container exposedPorts, portBindings, err := nat.ParsePortSpecs(builder.ports) if err != nil { - return "", fmt.Errorf("Failed to parse ports %v: %v", builder.ports, err) + return "", fmt.Errorf("unabel to parse ports: %v", err) } - if len(portBindings) > 0 { + time.Sleep(1 * time.Second) builder.containerConfig.ExposedPorts = exposedPorts builder.hostConfig.PortBindings = portBindings } - // add endpoint settings to existing networks - networkExist := make(map[string]bool, 0) - for networkName := range builder.networks { - networkExist[networkName] = false - } - - networks, err := builder.client.NetworkList(ctx, types.NetworkListOptions{}) - if err != nil { - return "", fmt.Errorf("Failed to list networks: %v", err) - } - - for _, nw := range networks { - if aliases, present := builder.networks[nw.Name]; present { - networkExist[nw.Name] = true - builder.networkConfig.EndpointsConfig[nw.Name] = &network.EndpointSettings{ - Aliases: aliases, - NetworkID: nw.ID, - } + // Network: Add container to container networks + // Add the container to the first defined container network, if any one is + // defined. If no one is defined, the docker API will add the container to + // their default bridge docker0. The other networks will be added to the + // container after the container start. + var ( + networkNames = make([]string, 0) + networks = make([]types.NetworkResource, 0) + ) + if len(builder.networks) > 0 { + for networkName := range builder.networks { + networkNames = append(networkNames, networkName) } - } - - for nw, found := range networkExist { - if !found { - return "", fmt.Errorf("Failed to add endpoint settings for network %v. It does not exist", nw) + var err error + networks, err = builder.client.NetworkListByNames(ctx, networkNames...) + if err != nil { + return "", err } + + endpointSetting := &network.EndpointSettings{ + NetworkID: networks[0].ID, + Aliases: builder.networks[networkNames[0]], + } + + builder.networkConfig.EndpointsConfig[networkNames[0]] = endpointSetting + + networkNames = networkNames[1:] + networks = networks[1:] } - // create container - resp, err := builder.client.ContainerCreate(ctx, builder.containerConfig, builder.hostConfig, builder.networkConfig, builder.containerName) + // Container: Create + resp, err := builder.client.ContainerCreate( + ctx, + builder.containerConfig, + builder.hostConfig, + builder.networkConfig, + builder.containerName, + ) if err != nil { - return "", fmt.Errorf("Failed to create container %v: %v", builder.containerName, err) + return "", fmt.Errorf("Unable to create container: %v", err) } - // start container err = builder.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) if err != nil { - stopError := builder.client.ContainerStopByIDs(ctx, time.Second, resp.ID) - if stopError != nil { - return "", fmt.Errorf("Failed to start container %v: %v\nUnable to remove container %v. Manual cleanup necessary", builder.containerName, err, builder.containerName) + + shutdownErr := builder.client.ContainerStopByIDs(ctx, 1*time.Second, resp.ID) + if shutdownErr != nil { + return "", fmt.Errorf("Unable to start container: %v\nUnable to remove container %s: %v\nManual cleanup necessary", err, resp.ID, shutdownErr) } - return "", fmt.Errorf("Failed to start container %v: %v", builder.containerName, err) + return "", fmt.Errorf("Unable to start container: %v", err) } - // wait for healthy - if builder.waitForHealthy { + // Network: Add more container networks + for i, networkName := range networkNames { + endpointSetting := &network.EndpointSettings{ + NetworkID: networks[i].ID, + Aliases: builder.networks[networkName], + } + err := builder.client.NetworkConnect(ctx, networks[i].ID, resp.ID, endpointSetting) + if err != nil { + return "", fmt.Errorf("Unable to append container endpoint to network %v", networkName) + } + } + if builder.waitForHealthy { watcher := builder.client.GetWatcher() - errorChannel := make(chan error, 1) - doneChannel := make(chan struct{}) - err = watcher.AddListener(resp.ID, errorChannel, doneChannel) + errors := make(chan error, 1) + done := make(chan struct{}) + err = watcher.AddListener(resp.ID, errors, done) if err != nil { containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) if containerRemoveError != nil { - return "", fmt.Errorf("Failed while watching status for container %v: %v\nUnable to remove container %v: %v", resp.ID, err, resp.ID, containerRemoveError) + return "", fmt.Errorf("error while watching for container status: %v - unable to remove container: %v", err, containerRemoveError) } - return "", fmt.Errorf("Failed while watching status for container %v: %v", resp.ID, err) + return "", fmt.Errorf("error while watching for container status: %v", err) } select { - case err := <-errorChannel: + case err := <-errors: if err != nil { containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) if containerRemoveError != nil { - return "", fmt.Errorf("Unable to remove container %v: %v", resp.ID, containerRemoveError) + return "", fmt.Errorf("%v - unable to remove container: %v", err, containerRemoveError) } return "", err } - case <-doneChannel: + case <-done: + } } - return resp.ID, nil + return resp.ID, err } func (builder *Builder) WaitForHealthy() *Builder { builder.waitForHealthy = true return builder } + +// WithName set the name of the container +func (builder *Builder) WithName(containerName string) *Builder { + builder.containerName = containerName + return builder +} diff --git a/pkg/testutils/dockerutils/client.go b/pkg/testutils/dockerutils/client.go index 47b295e..20eea8a 100644 --- a/pkg/testutils/dockerutils/client.go +++ b/pkg/testutils/dockerutils/client.go @@ -13,9 +13,11 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" ) +// Client from the docker API with additional functions type Client struct { *client.Client watcher *Watcher @@ -33,9 +35,9 @@ func (client *Client) Close() error { } // ContainerListByLabels returns only containers which match by given labels -func (client *Client) ContainerListByLabels(ctx context.Context, all bool, labels map[string]string) ([]types.Container, error) { +func (client *Client) ContainerListByLabels(ctx context.Context, all bool, containerLabels map[string]string) ([]types.Container, error) { filterArgs := filters.NewArgs() - for key, value := range labels { + for key, value := range containerLabels { filterArgs.Add("label", fmt.Sprintf("%v=%v", key, value)) } containers, err := client.ContainerList(ctx, types.ContainerListOptions{ @@ -52,9 +54,9 @@ func (client *Client) ContainerListByLabels(ctx context.Context, all bool, label } // ContainerListByNames returns only containers which match by given labels -func (client *Client) ContainerListByNames(ctx context.Context, all bool, names ...string) ([]types.Container, error) { +func (client *Client) ContainerListByNames(ctx context.Context, all bool, containerNames ...string) ([]types.Container, error) { filterArgs := filters.NewArgs() - for _, name := range names { + for _, name := range containerNames { filterArgs.Add("name", name) } containers, err := client.ContainerList(ctx, types.ContainerListOptions{ @@ -82,8 +84,8 @@ func (client *Client) ContainerRemoveByIDs(ctx context.Context, containerIDs ... } // ContainerRemoveByLabels deletes all containers which match by given labels -func (client *Client) ContainerRemoveByLabels(ctx context.Context, labels map[string]string) error { - containers, err := client.ContainerListByLabels(ctx, true, labels) +func (client *Client) ContainerRemoveByLabels(ctx context.Context, containerLabels map[string]string) error { + containers, err := client.ContainerListByLabels(ctx, true, containerLabels) if err != nil { return err } @@ -97,8 +99,8 @@ func (client *Client) ContainerRemoveByLabels(ctx context.Context, labels map[st } // ContainerRemoveByNames deletes all containers which match by their names -func (client *Client) ContainerRemoveByNames(ctx context.Context, names ...string) error { - containers, err := client.ContainerListByNames(ctx, true, names...) +func (client *Client) ContainerRemoveByNames(ctx context.Context, containerNames ...string) error { + containers, err := client.ContainerListByNames(ctx, true, containerNames...) if err != nil { return err } @@ -123,8 +125,23 @@ func (client *Client) ContainerStopByIDs(ctx context.Context, timeout time.Durat } // ContainerStopByLabels shutdown containters which match by given labels -func (client *Client) ContainerStopByLabels(ctx context.Context, timeout time.Duration, labels map[string]string) error { - containers, err := client.ContainerListByLabels(ctx, true, labels) +func (client *Client) ContainerStopByLabels(ctx context.Context, timeout time.Duration, containerLabels map[string]string) error { + containers, err := client.ContainerListByLabels(ctx, true, containerLabels) + if err != nil { + return err + } + for _, container := range containers { + err := client.ContainerStop(ctx, container.ID, &timeout) + if err != nil { + return err + } + } + return nil +} + +// ContainerStopByNames shutdown containters matching by their names +func (client *Client) ContainerStopByNames(ctx context.Context, timeout time.Duration, containerNames ...string) error { + containers, err := client.ContainerListByNames(ctx, true, containerNames...) if err != nil { return err } @@ -158,6 +175,96 @@ func (client *Client) GetWatcher() *Watcher { return client.watcher } +// NetworkListByLabels returns networks which match by given labels +func (client *Client) NetworkListByLabels(ctx context.Context, networkLabels map[string]string) ([]types.NetworkResource, error) { + args := filters.NewArgs() + for key, value := range networkLabels { + args.Add("label", fmt.Sprintf("%v=%v", key, value)) + } + + return client.NetworkList(ctx, types.NetworkListOptions{ + Filters: args, + }) +} + +// NetworkListByNames returns networks which match by their names. If a +// network can not be found, the function returns an error +func (client *Client) NetworkListByNames(ctx context.Context, networkNames ...string) ([]types.NetworkResource, error) { + networks, err := client.NetworkList(ctx, types.NetworkListOptions{}) + if err != nil { + return nil, err + } + + foundNetwork := make(map[string]bool, 0) + for _, networkName := range networkNames { + foundNetwork[networkName] = false + } + + filteredNetworks := make([]types.NetworkResource, 0) + for _, networkName := range networkNames { + for _, network := range networks { + if network.Name == networkName { + filteredNetworks = append(filteredNetworks, network) + foundNetwork[networkName] = true + } + } + } + + for _, networkName := range networkNames { + if !foundNetwork[networkName] { + return nil, fmt.Errorf("Network %v not found", networkName) + } + } + + return filteredNetworks, nil +} + +// NetworkRemoveByLabels remove all networks which match by given labels +func (client *Client) NetworkRemoveByLabels(ctx context.Context, containerLabels map[string]string) error { + networks, err := client.NetworkListByLabels(ctx, containerLabels) + if err != nil { + return err + } + + for _, network := range networks { + err := client.NetworkRemove(ctx, network.ID) + if err != nil { + return err + } + } + + return nil +} + +// NetworkRemoveByNames remove all networks match by their names. If a +// network can not be found, the function returns an error +func (client *Client) NetworkRemoveByNames(ctx context.Context, networkNames ...string) error { + networks, err := client.NetworkListByNames(ctx, networkNames...) + if err != nil { + return err + } + + for _, network := range networks { + err := client.NetworkRemove(ctx, network.ID) + if err != nil { + return err + } + } + + return nil +} + +// NetworkRemoveByIDs remove all networks match by their id +func (client *Client) NetworkRemoveByIDs(ctx context.Context, containerIDs ...string) error { + for _, containerID := range containerIDs { + err := client.NetworkRemove(ctx, containerID) + if err != nil { + return err + } + } + return nil +} + // NewBuilder returns a new builder for containers func (client *Client) NewBuilder(image string) *Builder { return &Builder{ @@ -165,8 +272,10 @@ func (client *Client) NewBuilder(image string) *Builder { containerConfig: &container.Config{ Image: image, }, - hostConfig: new(container.HostConfig), - networkConfig: new(network.NetworkingConfig), + hostConfig: new(container.HostConfig), + networkConfig: &network.NetworkingConfig{ + EndpointsConfig: make(map[string]*network.EndpointSettings, 0), + }, networks: make(map[string][]string, 0), ports: make([]string, 0), pull: false, @@ -206,6 +315,78 @@ func (client *Client) PullQuiet(ctx context.Context, image string) error { return client.Pull(ctx, image, ioutil.Discard) } +// VolumeListByLabels returns volumes which match by given labels +func (client *Client) VolumeListByLabels(ctx context.Context, volumeLabels map[string]string) (volume.VolumesListOKBody, error) { + args := filters.NewArgs() + for key, value := range volumeLabels { + args.Add("label", fmt.Sprintf("%v=%v", key, value)) + } + + return client.VolumeList(ctx, args) +} + +// VolumeListByNames returns volumes which match by their names. If a +// volume can not be found, the function returns an error +func (client *Client) VolumeListByNames(ctx context.Context, volumeNames ...string) (volume.VolumesListOKBody, error) { + args := filters.NewArgs() + foundVolumes := make(map[string]bool, 0) + for _, volumeName := range volumeNames { + foundVolumes[volumeName] = false + args.Add("name", volumeName) + } + + volumes, err := client.VolumeList(ctx, args) + if err != nil { + return volume.VolumesListOKBody{}, err + } + + for _, volume := range volumes.Volumes { + foundVolumes[volume.Name] = true + } + + for _, volumeName := range volumeNames { + if foundVolumes[volumeName] != true { + return volume.VolumesListOKBody{}, fmt.Errorf("Volume %v not found", volumeName) + } + } + return volumes, err +} + +// VolumeRemoveByLabels remove all volumes match by their labels +func (client *Client) VolumeRemoveByLabels(ctx context.Context, volumeLabels map[string]string) error { + volumes, err := client.VolumeListByLabels(ctx, volumeLabels) + if err != nil { + return err + } + + for _, volume := range volumes.Volumes { + err := client.VolumeRemove(ctx, volume.Name, true) + if err != nil { + return err + } + } + + return nil +} + +// VolumeRemoveByNames remove all volumes match by their names. If a +// volume can not be found, the function returns an error +func (client *Client) VolumeRemoveByNames(ctx context.Context, volumeNames ...string) error { + volumes, err := client.VolumeListByNames(ctx, volumeNames...) + if err != nil { + return err + } + + for _, volume := range volumes.Volumes { + err := client.VolumeRemove(ctx, volume.Name, true) + if err != nil { + return err + } + } + + return nil +} + // New returns a new dockerutil client func New() (*Client, error) { dockerClient, err := client.NewEnvClient() diff --git a/pkg/testutils/dockerutils/client_test.go b/pkg/testutils/dockerutils/client_test.go new file mode 100644 index 0000000..e69d921 --- /dev/null +++ b/pkg/testutils/dockerutils/client_test.go @@ -0,0 +1,443 @@ +package dockerutils + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/volume" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/require" +) + +// TestContainerCRUD +// Test the following API functions: +// - ContainerListByLabels +// - ContainerListByNames +// - ContainerRemoveByNames +// - ContainerRemoveByLabels +// - ContainerRemoveByIDs +func TestContainerCRUD(t *testing.T) { + var ( + ctx = context.Background() + require = require.New(t) + + iterations = 5 + + cleanupLabels = map[string]string{ + uuid.NewV4().String(): uuid.NewV4().String(), + } + ) + + dockerClient, err := New() + require.NoError(err) + t.Cleanup(func() { + dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) + }) + + // Create Containers + containerIDs := make([]string, 0) + containerNames := make([]string, 0) + for i := 0; i < iterations; i++ { + containerName := uuid.NewV4().String() + containerID, err := dockerClient.NewBuilder("nginx:alpine"). + Labels(cleanupLabels). + Port("80"). + Pull(). + WithName(containerName). + Start(ctx) + require.NoError(err) + + containerNames = append(containerNames, containerName) + containerIDs = append(containerIDs, containerID) + } + + // ListByLabels + containers, err := dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) + require.NoError(err) + require.Len(containers, iterations) + for _, container := range containers { + require.Contains(containerIDs, container.ID) + require.Contains(containerNames, strings.Split(container.Names[0], "/")[1]) + } + + // ListByNames + containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...) + require.NoError(err) + require.Len(containers, iterations) + for _, container := range containers { + require.Contains(containerIDs, container.ID) + require.Contains(containerNames, strings.Split(container.Names[0], "/")[1]) + } + + // RemoveByLabels + err = dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) + require.NoError(err) + + containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) + require.NoError(err) + require.Len(containers, 0) + + // Create + containerIDs = make([]string, 0) + containerNames = make([]string, 0) + for i := 0; i < iterations; i++ { + containerName := uuid.NewV4().String() + containerID, err := dockerClient.NewBuilder("nginx:alpine"). + Labels(cleanupLabels). + Port("80"). + Pull(). + WithName(containerName). + Start(ctx) + require.NoError(err) + + containerNames = append(containerNames, containerName) + containerIDs = append(containerIDs, containerID) + } + + // RemoveByNames + err = dockerClient.ContainerRemoveByNames(ctx, containerNames...) + require.NoError(err) + + containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...) + require.NoError(err) + require.Len(containers, 0) + + // Create + containerIDs = make([]string, 0) + containerNames = make([]string, 0) + for i := 0; i < iterations; i++ { + containerName := uuid.NewV4().String() + containerID, err := dockerClient.NewBuilder("nginx:alpine"). + Labels(cleanupLabels). + Port("80"). + Pull(). + WithName(containerName). + Start(ctx) + require.NoError(err) + + containerNames = append(containerNames, containerName) + containerIDs = append(containerIDs, containerID) + } + + // RemoveByID + err = dockerClient.ContainerRemoveByIDs(ctx, containerIDs...) + require.NoError(err) + + containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) + require.NoError(err) + require.Len(containers, 0) +} + +// TestNetworkCRUD +// Test the following API functions: +// - NetworkListByLabels +// - NetworkListByNames +// - NetworkRemoveByLabels +// - NetworkRemoveByNames +// - NetworkRemoveByIDs +func TestNetworkCRUD(t *testing.T) { + var ( + ctx = context.Background() + require = require.New(t) + + iterations = 5 + + cleanupLabels = map[string]string{ + uuid.NewV4().String(): uuid.NewV4().String(), + } + ) + + dockerClient, err := New() + require.NoError(err) + t.Cleanup(func() { + dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) + }) + + // Create Networks + networkIDs := make([]string, 0) + networkNames := make([]string, 0) + for i := 0; i < iterations; i++ { + networkName := uuid.NewV4().String() + resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ + Labels: cleanupLabels, + }) + require.NoError(err) + + networkNames = append(networkNames, networkName) + networkIDs = append(networkIDs, resp.ID) + } + + // ListByLabels + networks, err := dockerClient.NetworkListByLabels(ctx, cleanupLabels) + require.NoError(err) + require.Len(networks, iterations) + for _, network := range networks { + require.Contains(networkIDs, network.ID) + require.Contains(networkNames, network.Name) + } + + // ListByLabels, network with label does not exist + networks, err = dockerClient.NetworkListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) + require.NoError(err) + require.Len(networks, 0) + + // ListByNames + networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) + require.NoError(err) + require.Len(networks, iterations) + for _, network := range networks { + require.Contains(networkIDs, network.ID) + require.Contains(networkNames, network.Name) + } + + // ListByNames, network with names does not exist + networks, err = dockerClient.NetworkListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String()) + require.Error(err) + require.Nil(networks) + + // RemoveByLabels + err = dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) + require.NoError(err) + + networks, err = dockerClient.NetworkListByLabels(ctx, cleanupLabels) + require.NoError(err) + require.Len(networks, 0) + + // RemoveByLabels, label does not exists + err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) + require.NoError(err) + + // Create Networks + networkIDs = make([]string, 0) + networkNames = make([]string, 0) + for i := 0; i < iterations; i++ { + networkName := uuid.NewV4().String() + resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ + Labels: cleanupLabels, + }) + require.NoError(err) + + networkNames = append(networkNames, networkName) + networkIDs = append(networkIDs, resp.ID) + } + + // RemoveByNames + err = dockerClient.NetworkRemoveByNames(ctx, networkNames...) + require.NoError(err) + + networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) + require.Error(err) + require.Nil(networks) + + // RemoveByNames, name does not exists + err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String()) + require.Error(err) + + // Create Networks + networkIDs = make([]string, 0) + networkNames = make([]string, 0) + for i := 0; i < iterations; i++ { + networkName := uuid.NewV4().String() + resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ + Labels: cleanupLabels, + }) + require.NoError(err) + + networkNames = append(networkNames, networkName) + networkIDs = append(networkIDs, resp.ID) + } + + // RemoveByIDs + err = dockerClient.NetworkRemoveByIDs(ctx, networkIDs...) + require.NoError(err) + + networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) + require.Error(err) + require.Nil(networks) + + // RemoveByID, id does not exists + err = dockerClient.NetworkRemoveByIDs(ctx, uuid.NewV4().String()) + require.Error(err) +} + +func TestVolumeCRUD(t *testing.T) { + var ( + ctx = context.Background() + require = require.New(t) + + iterations = 5 + + cleanupLabels = map[string]string{ + uuid.NewV4().String(): uuid.NewV4().String(), + } + ) + + dockerClient, err := New() + require.NoError(err) + t.Cleanup(func() { + dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels) + }) + + // Create Volumes + volumeNames := make([]string, 0) + for i := 0; i < iterations; i++ { + volumeName := uuid.NewV4().String() + volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{ + Name: volumeName, + Labels: cleanupLabels, + }) + require.NoError(err) + + volumeNames = append(volumeNames, volume.Name) + } + + // ListByLabels + volumes, err := dockerClient.VolumeListByLabels(ctx, cleanupLabels) + require.NoError(err) + require.Len(volumes.Volumes, iterations) + for _, volume := range volumes.Volumes { + require.Contains(volumeNames, volume.Name) + } + + // ListByLabels, network with label does not exist + volumes, err = dockerClient.VolumeListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) + require.NoError(err) + require.Len(volumes.Volumes, 0) + + // ListByNames + volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...) + require.NoError(err) + require.Len(volumes.Volumes, iterations) + for _, volume := range volumes.Volumes { + require.Contains(volumeNames, volume.Name) + } + + // ListByNames, network with names does not exist + volumes, err = dockerClient.VolumeListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String()) + require.Error(err) + require.Nil(volumes.Volumes) + + // RemoveByLabels + err = dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels) + require.NoError(err) + + volumes, err = dockerClient.VolumeListByLabels(ctx, cleanupLabels) + require.NoError(err) + require.Len(volumes.Volumes, 0) + + // RemoveByLabels, labels does not exists + err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) + require.NoError(err) + + // Create Volumes + volumeNames = make([]string, 0) + for i := 0; i < iterations; i++ { + volumeName := uuid.NewV4().String() + volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{ + Name: volumeName, + Labels: cleanupLabels, + }) + require.NoError(err) + + volumeNames = append(volumeNames, volume.Name) + } + + // RemoveByNames + err = dockerClient.VolumeRemoveByNames(ctx, volumeNames...) + require.NoError(err) + + volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...) + require.Error(err) + require.Nil(volumes.Volumes) + + // RemoveByNames, name does not exists + err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String()) + require.Error(err) +} + +// TestContainerMultipleNetworks +// Test if a container can be accessed ofer multiple networks/ips. +func TestContainerMultipleNetworks(t *testing.T) { + var ( + ctx = context.Background() + require = require.New(t) + + iterations = 5 + + cleanupLabels = map[string]string{ + uuid.NewV4().String(): uuid.NewV4().String(), + } + ) + + dockerClient, err := New() + require.NoError(err) + t.Cleanup(func() { + dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) + dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) + }) + + // Create Containers + containerIDs := make([]string, 0) + containerNames := make([]string, 0) + containersNetworks := make(map[string]map[string][]string, 0) + for i := 0; i < iterations; i++ { + containerName := uuid.NewV4().String() + + containerNetworks := map[string][]string{ + uuid.NewV4().String(): { + uuid.NewV4().String(), + uuid.NewV4().String(), + }, + uuid.NewV4().String(): { + uuid.NewV4().String(), + uuid.NewV4().String(), + }, + } + + builder := dockerClient.NewBuilder("nginx:alpine"). + Labels(cleanupLabels). + Port("80"). + Pull(). + WithName(containerName) + + for networkName, aliasses := range containerNetworks { + _, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ + Labels: cleanupLabels, + }) + require.NoError(err) + + builder.Network(networkName, aliasses...) + } + + containerID, err := builder.Start(ctx) + require.NoError(err) + + containerNames = append(containerNames, containerName) + containerIDs = append(containerIDs, containerID) + containersNetworks[containerID] = containerNetworks + } + + for containerID, containerNetworks := range containersNetworks { + for networkName := range containerNetworks { + networks, err := dockerClient.NetworkListByNames(ctx, networkName) + require.NoError(err) + for _, network := range networks { + if _, present := network.Containers[containerID]; !present { + require.Fail("Container %v not found in network %v", containerID, network.ID) + } + + networkIPParts := strings.Split(network.Containers[containerID].IPv4Address, "/") + + url := fmt.Sprintf("http://%v", networkIPParts[0]) + resp, err := http.Get(url) + require.NoError(err) + defer resp.Body.Close() + require.Equal(http.StatusOK, resp.StatusCode) + } + } + } +}