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