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