clean.go 6.1 KB

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