github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+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/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 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 len(tags) == 0 { 102 removeImage(ctx, t, apiclient, image.ID) 103 continue 104 } 105 for _, tag := range tags { 106 if _, ok := protectedImages[tag]; !ok { 107 removeImage(ctx, t, apiclient, tag) 108 } 109 } 110 } 111 } 112 113 func removeImage(ctx context.Context, t testing.TB, apiclient client.ImageAPIClient, ref string) { 114 t.Helper() 115 _, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{ 116 Force: true, 117 }) 118 if client.IsErrNotFound(err) { 119 return 120 } 121 assert.Check(t, err, "failed to remove image %s", ref) 122 } 123 124 func deleteAllVolumes(t testing.TB, c client.VolumeAPIClient, protectedVolumes map[string]struct{}) { 125 t.Helper() 126 volumes, err := c.VolumeList(context.Background(), filters.Args{}) 127 assert.Check(t, err, "failed to list volumes") 128 129 for _, v := range volumes.Volumes { 130 if _, ok := protectedVolumes[v.Name]; ok { 131 continue 132 } 133 err := c.VolumeRemove(context.Background(), v.Name, true) 134 // Docker EE may list volumes that no longer exist. 135 if isErrNotFoundSwarmClassic(err) { 136 continue 137 } 138 assert.Check(t, err, "failed to remove volume %s", v.Name) 139 } 140 } 141 142 func deleteAllNetworks(t testing.TB, c client.NetworkAPIClient, daemonPlatform string, protectedNetworks map[string]struct{}) { 143 t.Helper() 144 networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{}) 145 assert.Check(t, err, "failed to list networks") 146 147 for _, n := range networks { 148 if n.Name == "bridge" || n.Name == "none" || n.Name == "host" { 149 continue 150 } 151 if _, ok := protectedNetworks[n.ID]; ok { 152 continue 153 } 154 if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" { 155 // nat is a pre-defined network on Windows and cannot be removed 156 continue 157 } 158 err := c.NetworkRemove(context.Background(), n.ID) 159 assert.Check(t, err, "failed to remove network %s", n.ID) 160 } 161 } 162 163 func deleteAllPlugins(t testing.TB, c client.PluginAPIClient, protectedPlugins map[string]struct{}) { 164 t.Helper() 165 plugins, err := c.PluginList(context.Background(), filters.Args{}) 166 // Docker EE does not allow cluster-wide plugin management. 167 if client.IsErrNotImplemented(err) { 168 return 169 } 170 assert.Check(t, err, "failed to list plugins") 171 172 for _, p := range plugins { 173 if _, ok := protectedPlugins[p.Name]; ok { 174 continue 175 } 176 err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true}) 177 assert.Check(t, err, "failed to remove plugin %s", p.ID) 178 } 179 } 180 181 // Swarm classic aggregates node errors and returns a 500 so we need to check 182 // the error string instead of just IsErrNotFound(). 183 func isErrNotFoundSwarmClassic(err error) bool { 184 return err != nil && strings.Contains(strings.ToLower(err.Error()), "no such") 185 }