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 }