diff --git a/internal/test/environment/clean.go b/internal/test/environment/clean.go index c6392dc1bc..702d10711b 100644 --- a/internal/test/environment/clean.go +++ b/internal/test/environment/clean.go @@ -32,12 +32,12 @@ func (e *Execution) Clean(t testingT) { if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") { unpauseAllContainers(t, client) } - deleteAllContainers(t, client) + deleteAllContainers(t, client, e.protectedElements.containers) deleteAllImages(t, client, e.protectedElements.images) - deleteAllVolumes(t, client) - deleteAllNetworks(t, client, platform) + deleteAllVolumes(t, client, e.protectedElements.volumes) + deleteAllNetworks(t, client, platform, e.protectedElements.networks) if platform == "linux" { - deleteAllPlugins(t, client) + deleteAllPlugins(t, client, e.protectedElements.plugins) } } @@ -66,7 +66,7 @@ func getPausedContainers(ctx context.Context, t assert.TestingT, client client.C var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`) -func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient) { +func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient, protectedContainers map[string]struct{}) { ctx := context.Background() containers := getAllContainers(ctx, t, apiclient) if len(containers) == 0 { @@ -74,6 +74,9 @@ func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient) } for _, container := range containers { + if _, ok := protectedContainers[container.ID]; ok { + continue + } err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{ Force: true, RemoveVolumes: true, @@ -126,17 +129,20 @@ func removeImage(ctx context.Context, t assert.TestingT, apiclient client.ImageA assert.NoError(t, err, "failed to remove image %s", ref) } -func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient) { +func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient, protectedVolumes map[string]struct{}) { volumes, err := c.VolumeList(context.Background(), filters.Args{}) assert.NoError(t, err, "failed to list volumes") for _, v := range volumes.Volumes { + if _, ok := protectedVolumes[v.Name]; ok { + continue + } err := c.VolumeRemove(context.Background(), v.Name, true) assert.NoError(t, err, "failed to remove volume %s", v.Name) } } -func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatform string) { +func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatform string, protectedNetworks map[string]struct{}) { networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{}) assert.NoError(t, err, "failed to list networks") @@ -144,6 +150,9 @@ func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatf if n.Name == "bridge" || n.Name == "none" || n.Name == "host" { continue } + if _, ok := protectedNetworks[n.ID]; ok { + continue + } if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" { // nat is a pre-defined network on Windows and cannot be removed continue @@ -153,11 +162,14 @@ func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatf } } -func deleteAllPlugins(t assert.TestingT, c client.PluginAPIClient) { +func deleteAllPlugins(t assert.TestingT, c client.PluginAPIClient, protectedPlugins map[string]struct{}) { plugins, err := c.PluginList(context.Background(), filters.Args{}) assert.NoError(t, err, "failed to list plugins") for _, p := range plugins { + if _, ok := protectedPlugins[p.Name]; ok { + continue + } err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true}) assert.NoError(t, err, "failed to remove plugin %s", p.ID) } diff --git a/internal/test/environment/protect.go b/internal/test/environment/protect.go index 5821863298..2e882c8470 100644 --- a/internal/test/environment/protect.go +++ b/internal/test/environment/protect.go @@ -10,7 +10,63 @@ import ( ) type protectedElements struct { - images map[string]struct{} + containers map[string]struct{} + images map[string]struct{} + networks map[string]struct{} + plugins map[string]struct{} + volumes map[string]struct{} +} + +func newProtectedElements() protectedElements { + return protectedElements{ + containers: map[string]struct{}{}, + images: map[string]struct{}{}, + networks: map[string]struct{}{}, + plugins: map[string]struct{}{}, + volumes: map[string]struct{}{}, + } +} + +// ProtectAll protects the existing environment (containers, images, networks, +// volumes, and, on Linux, plugins) from being cleaned up at the end of test +// runs +func ProtectAll(t testingT, testEnv *Execution) { + ProtectContainers(t, testEnv) + ProtectImages(t, testEnv) + ProtectNetworks(t, testEnv) + ProtectVolumes(t, testEnv) + if testEnv.DaemonInfo.OSType == "linux" { + ProtectPlugins(t, testEnv) + } +} + +// ProtectContainer adds the specified container(s) to be protected in case of +// clean +func (e *Execution) ProtectContainer(t testingT, containers ...string) { + for _, container := range containers { + e.protectedElements.containers[container] = struct{}{} + } +} + +// ProtectContainers protects existing containers from being cleaned up at the +// end of test runs +func ProtectContainers(t testingT, testEnv *Execution) { + containers := getExistingContainers(t, testEnv) + testEnv.ProtectContainer(t, containers...) +} + +func getExistingContainers(t require.TestingT, testEnv *Execution) []string { + client := testEnv.APIClient() + containerList, err := client.ContainerList(context.Background(), types.ContainerListOptions{ + All: true, + }) + require.NoError(t, err, "failed to list containers") + + containers := []string{} + for _, container := range containerList { + containers = append(containers, container.ID) + } + return containers } // ProtectImage adds the specified image(s) to be protected in case of clean @@ -20,12 +76,6 @@ func (e *Execution) ProtectImage(t testingT, images ...string) { } } -func newProtectedElements() protectedElements { - return protectedElements{ - images: map[string]struct{}{}, - } -} - // ProtectImages protects existing images and on linux frozen images from being // cleaned up at the end of test runs func ProtectImages(t testingT, testEnv *Execution) { @@ -42,6 +92,7 @@ func getExistingImages(t require.TestingT, testEnv *Execution) []string { filter := filters.NewArgs() filter.Add("dangling", "false") imageList, err := client.ImageList(context.Background(), types.ImageListOptions{ + All: true, Filters: filter, }) require.NoError(t, err, "failed to list images") @@ -76,3 +127,82 @@ func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string { } return images } + +// ProtectNetwork adds the specified network(s) to be protected in case of +// clean +func (e *Execution) ProtectNetwork(t testingT, networks ...string) { + for _, network := range networks { + e.protectedElements.networks[network] = struct{}{} + } +} + +// ProtectNetworks protects existing networks from being cleaned up at the end +// of test runs +func ProtectNetworks(t testingT, testEnv *Execution) { + networks := getExistingNetworks(t, testEnv) + testEnv.ProtectNetwork(t, networks...) +} + +func getExistingNetworks(t require.TestingT, testEnv *Execution) []string { + client := testEnv.APIClient() + networkList, err := client.NetworkList(context.Background(), types.NetworkListOptions{}) + require.NoError(t, err, "failed to list networks") + + networks := []string{} + for _, network := range networkList { + networks = append(networks, network.ID) + } + return networks +} + +// ProtectPlugin adds the specified plugin(s) to be protected in case of clean +func (e *Execution) ProtectPlugin(t testingT, plugins ...string) { + for _, plugin := range plugins { + e.protectedElements.plugins[plugin] = struct{}{} + } +} + +// ProtectPlugins protects existing plugins from being cleaned up at the end of +// test runs +func ProtectPlugins(t testingT, testEnv *Execution) { + plugins := getExistingPlugins(t, testEnv) + testEnv.ProtectPlugin(t, plugins...) +} + +func getExistingPlugins(t require.TestingT, testEnv *Execution) []string { + client := testEnv.APIClient() + pluginList, err := client.PluginList(context.Background(), filters.Args{}) + require.NoError(t, err, "failed to list plugins") + + plugins := []string{} + for _, plugin := range pluginList { + plugins = append(plugins, plugin.Name) + } + return plugins +} + +// ProtectVolume adds the specified volume(s) to be protected in case of clean +func (e *Execution) ProtectVolume(t testingT, volumes ...string) { + for _, volume := range volumes { + e.protectedElements.volumes[volume] = struct{}{} + } +} + +// ProtectVolumes protects existing volumes from being cleaned up at the end of +// test runs +func ProtectVolumes(t testingT, testEnv *Execution) { + volumes := getExistingVolumes(t, testEnv) + testEnv.ProtectVolume(t, volumes...) +} + +func getExistingVolumes(t require.TestingT, testEnv *Execution) []string { + client := testEnv.APIClient() + volumeList, err := client.VolumeList(context.Background(), filters.Args{}) + require.NoError(t, err, "failed to list volumes") + + volumes := []string{} + for _, volume := range volumeList.Volumes { + volumes = append(volumes, volume.Name) + } + return volumes +}