github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/testutil/environment/clean.go (about)

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