github.com/rawahars/moby@v24.0.4+incompatible/testutil/environment/clean.go (about)

     1  package environment // import "github.com/docker/docker/testutil/environment"
     2  
     3  import (
     4  	"context"
     5  	"regexp"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/filters"
    11  	"github.com/docker/docker/api/types/volume"
    12  	"github.com/docker/docker/client"
    13  	"github.com/docker/docker/errdefs"
    14  	"gotest.tools/v3/assert"
    15  )
    16  
    17  // Clean the environment, preserving protected objects (images, containers, ...)
    18  // and removing everything else. It's meant to run after any tests so that they don't
    19  // depend on each others.
    20  func (e *Execution) Clean(t testing.TB) {
    21  	t.Helper()
    22  	client := e.APIClient()
    23  
    24  	platform := e.OSType
    25  	if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") {
    26  		unpauseAllContainers(t, client)
    27  	}
    28  	deleteAllContainers(t, client, e.protectedElements.containers)
    29  	deleteAllImages(t, client, e.protectedElements.images)
    30  	deleteAllVolumes(t, client, e.protectedElements.volumes)
    31  	deleteAllNetworks(t, client, platform, e.protectedElements.networks)
    32  	if platform == "linux" {
    33  		deleteAllPlugins(t, client, e.protectedElements.plugins)
    34  	}
    35  }
    36  
    37  func unpauseAllContainers(t testing.TB, client client.ContainerAPIClient) {
    38  	t.Helper()
    39  	ctx := context.Background()
    40  	containers := getPausedContainers(ctx, t, client)
    41  	if len(containers) > 0 {
    42  		for _, container := range containers {
    43  			err := client.ContainerUnpause(ctx, container.ID)
    44  			assert.Check(t, err, "failed to unpause container %s", container.ID)
    45  		}
    46  	}
    47  }
    48  
    49  func getPausedContainers(ctx context.Context, t testing.TB, client client.ContainerAPIClient) []types.Container {
    50  	t.Helper()
    51  	containers, err := client.ContainerList(ctx, types.ContainerListOptions{
    52  		Filters: filters.NewArgs(filters.Arg("status", "paused")),
    53  		All:     true,
    54  	})
    55  	assert.Check(t, err, "failed to list containers")
    56  	return containers
    57  }
    58  
    59  var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
    60  
    61  func deleteAllContainers(t testing.TB, apiclient client.ContainerAPIClient, protectedContainers map[string]struct{}) {
    62  	t.Helper()
    63  	ctx := context.Background()
    64  	containers := getAllContainers(ctx, t, apiclient)
    65  	if len(containers) == 0 {
    66  		return
    67  	}
    68  
    69  	for _, container := range containers {
    70  		if _, ok := protectedContainers[container.ID]; ok {
    71  			continue
    72  		}
    73  		err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
    74  			Force:         true,
    75  			RemoveVolumes: true,
    76  		})
    77  		if err == nil || client.IsErrNotFound(err) || alreadyExists.MatchString(err.Error()) || isErrNotFoundSwarmClassic(err) {
    78  			continue
    79  		}
    80  		assert.Check(t, err, "failed to remove %s", container.ID)
    81  	}
    82  }
    83  
    84  func getAllContainers(ctx context.Context, t testing.TB, client client.ContainerAPIClient) []types.Container {
    85  	t.Helper()
    86  	containers, err := client.ContainerList(ctx, types.ContainerListOptions{
    87  		All: true,
    88  	})
    89  	assert.Check(t, err, "failed to list containers")
    90  	return containers
    91  }
    92  
    93  func deleteAllImages(t testing.TB, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
    94  	t.Helper()
    95  	images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
    96  	assert.Check(t, err, "failed to list images")
    97  
    98  	ctx := context.Background()
    99  	for _, image := range images {
   100  		tags := tagsFromImageSummary(image)
   101  		if _, ok := protectedImages[image.ID]; ok {
   102  			continue
   103  		}
   104  		if len(tags) == 0 {
   105  			removeImage(ctx, t, apiclient, image.ID)
   106  			continue
   107  		}
   108  		for _, tag := range tags {
   109  			if _, ok := protectedImages[tag]; !ok {
   110  				removeImage(ctx, t, apiclient, tag)
   111  			}
   112  		}
   113  	}
   114  }
   115  
   116  func removeImage(ctx context.Context, t testing.TB, apiclient client.ImageAPIClient, ref string) {
   117  	t.Helper()
   118  	_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
   119  		Force: true,
   120  	})
   121  	if client.IsErrNotFound(err) {
   122  		return
   123  	}
   124  	assert.Check(t, err, "failed to remove image %s", ref)
   125  }
   126  
   127  func deleteAllVolumes(t testing.TB, c client.VolumeAPIClient, protectedVolumes map[string]struct{}) {
   128  	t.Helper()
   129  	volumes, err := c.VolumeList(context.Background(), volume.ListOptions{})
   130  	assert.Check(t, err, "failed to list volumes")
   131  
   132  	for _, v := range volumes.Volumes {
   133  		if _, ok := protectedVolumes[v.Name]; ok {
   134  			continue
   135  		}
   136  		err := c.VolumeRemove(context.Background(), v.Name, true)
   137  		// Docker EE may list volumes that no longer exist.
   138  		if isErrNotFoundSwarmClassic(err) {
   139  			continue
   140  		}
   141  		assert.Check(t, err, "failed to remove volume %s", v.Name)
   142  	}
   143  }
   144  
   145  func deleteAllNetworks(t testing.TB, c client.NetworkAPIClient, daemonPlatform string, protectedNetworks map[string]struct{}) {
   146  	t.Helper()
   147  	networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
   148  	assert.Check(t, err, "failed to list networks")
   149  
   150  	for _, n := range networks {
   151  		if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
   152  			continue
   153  		}
   154  		if _, ok := protectedNetworks[n.ID]; ok {
   155  			continue
   156  		}
   157  		if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
   158  			// nat is a pre-defined network on Windows and cannot be removed
   159  			continue
   160  		}
   161  		err := c.NetworkRemove(context.Background(), n.ID)
   162  		assert.Check(t, err, "failed to remove network %s", n.ID)
   163  	}
   164  }
   165  
   166  func deleteAllPlugins(t testing.TB, c client.PluginAPIClient, protectedPlugins map[string]struct{}) {
   167  	t.Helper()
   168  	plugins, err := c.PluginList(context.Background(), filters.Args{})
   169  	// Docker EE does not allow cluster-wide plugin management.
   170  	if errdefs.IsNotImplemented(err) {
   171  		return
   172  	}
   173  	assert.Check(t, err, "failed to list plugins")
   174  
   175  	for _, p := range plugins {
   176  		if _, ok := protectedPlugins[p.Name]; ok {
   177  			continue
   178  		}
   179  		err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
   180  		assert.Check(t, err, "failed to remove plugin %s", p.ID)
   181  	}
   182  }
   183  
   184  // Swarm classic aggregates node errors and returns a 500 so we need to check
   185  // the error string instead of just IsErrNotFound().
   186  func isErrNotFoundSwarmClassic(err error) bool {
   187  	return err != nil && strings.Contains(strings.ToLower(err.Error()), "no such")
   188  }