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