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