protect.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package environment
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/docker/docker/api/types"
  6. "github.com/docker/docker/api/types/container"
  7. "github.com/docker/docker/api/types/filters"
  8. "github.com/docker/docker/api/types/image"
  9. "github.com/docker/docker/api/types/volume"
  10. "github.com/docker/docker/errdefs"
  11. "go.opentelemetry.io/otel"
  12. "gotest.tools/v3/assert"
  13. )
  14. var frozenImages = []string{"busybox:latest", "busybox:glibc", "hello-world:frozen", "debian:bookworm-slim"}
  15. type protectedElements struct {
  16. containers map[string]struct{}
  17. images map[string]struct{}
  18. networks map[string]struct{}
  19. plugins map[string]struct{}
  20. volumes map[string]struct{}
  21. }
  22. func newProtectedElements() protectedElements {
  23. return protectedElements{
  24. containers: map[string]struct{}{},
  25. images: map[string]struct{}{},
  26. networks: map[string]struct{}{},
  27. plugins: map[string]struct{}{},
  28. volumes: map[string]struct{}{},
  29. }
  30. }
  31. // ProtectAll protects the existing environment (containers, images, networks,
  32. // volumes, and, on Linux, plugins) from being cleaned up at the end of test
  33. // runs
  34. func ProtectAll(ctx context.Context, t testing.TB, testEnv *Execution) {
  35. t.Helper()
  36. ctx, span := otel.Tracer("").Start(ctx, "ProtectAll")
  37. defer span.End()
  38. ProtectContainers(ctx, t, testEnv)
  39. ProtectImages(ctx, t, testEnv)
  40. ProtectNetworks(ctx, t, testEnv)
  41. ProtectVolumes(ctx, t, testEnv)
  42. if testEnv.DaemonInfo.OSType == "linux" {
  43. ProtectPlugins(ctx, t, testEnv)
  44. }
  45. }
  46. // ProtectContainer adds the specified container(s) to be protected in case of
  47. // clean
  48. func (e *Execution) ProtectContainer(t testing.TB, containers ...string) {
  49. t.Helper()
  50. for _, container := range containers {
  51. e.protectedElements.containers[container] = struct{}{}
  52. }
  53. }
  54. // ProtectContainers protects existing containers from being cleaned up at the
  55. // end of test runs
  56. func ProtectContainers(ctx context.Context, t testing.TB, testEnv *Execution) {
  57. t.Helper()
  58. containers := getExistingContainers(ctx, t, testEnv)
  59. testEnv.ProtectContainer(t, containers...)
  60. }
  61. func getExistingContainers(ctx context.Context, t testing.TB, testEnv *Execution) []string {
  62. t.Helper()
  63. client := testEnv.APIClient()
  64. containerList, err := client.ContainerList(ctx, container.ListOptions{
  65. All: true,
  66. })
  67. assert.NilError(t, err, "failed to list containers")
  68. var containers []string
  69. for _, container := range containerList {
  70. containers = append(containers, container.ID)
  71. }
  72. return containers
  73. }
  74. // ProtectImage adds the specified image(s) to be protected in case of clean
  75. func (e *Execution) ProtectImage(t testing.TB, images ...string) {
  76. t.Helper()
  77. for _, image := range images {
  78. e.protectedElements.images[image] = struct{}{}
  79. }
  80. }
  81. // ProtectImages protects existing images and on linux frozen images from being
  82. // cleaned up at the end of test runs
  83. func ProtectImages(ctx context.Context, t testing.TB, testEnv *Execution) {
  84. t.Helper()
  85. images := getExistingImages(ctx, t, testEnv)
  86. if testEnv.DaemonInfo.OSType == "linux" {
  87. images = append(images, frozenImages...)
  88. }
  89. testEnv.ProtectImage(t, images...)
  90. testEnv.ProtectImage(t, DanglingImageIdGraphDriver, DanglingImageIdSnapshotter)
  91. }
  92. func getExistingImages(ctx context.Context, t testing.TB, testEnv *Execution) []string {
  93. t.Helper()
  94. client := testEnv.APIClient()
  95. imageList, err := client.ImageList(ctx, types.ImageListOptions{
  96. All: true,
  97. Filters: filters.NewArgs(filters.Arg("dangling", "false")),
  98. })
  99. assert.NilError(t, err, "failed to list images")
  100. var images []string
  101. for _, img := range imageList {
  102. images = append(images, tagsFromImageSummary(img)...)
  103. }
  104. return images
  105. }
  106. func tagsFromImageSummary(image image.Summary) []string {
  107. var result []string
  108. for _, tag := range image.RepoTags {
  109. // Starting from API 1.43 no longer outputs the hardcoded <none>
  110. // strings. But since the tests might be ran against a remote
  111. // daemon/pre 1.43 CLI we must still be able to handle it.
  112. if tag != "<none>:<none>" {
  113. result = append(result, tag)
  114. }
  115. }
  116. for _, digest := range image.RepoDigests {
  117. if digest != "<none>@<none>" {
  118. result = append(result, digest)
  119. }
  120. }
  121. return result
  122. }
  123. // ProtectNetwork adds the specified network(s) to be protected in case of
  124. // clean
  125. func (e *Execution) ProtectNetwork(t testing.TB, networks ...string) {
  126. t.Helper()
  127. for _, network := range networks {
  128. e.protectedElements.networks[network] = struct{}{}
  129. }
  130. }
  131. // ProtectNetworks protects existing networks from being cleaned up at the end
  132. // of test runs
  133. func ProtectNetworks(ctx context.Context, t testing.TB, testEnv *Execution) {
  134. t.Helper()
  135. networks := getExistingNetworks(ctx, t, testEnv)
  136. testEnv.ProtectNetwork(t, networks...)
  137. }
  138. func getExistingNetworks(ctx context.Context, t testing.TB, testEnv *Execution) []string {
  139. t.Helper()
  140. client := testEnv.APIClient()
  141. networkList, err := client.NetworkList(ctx, types.NetworkListOptions{})
  142. assert.NilError(t, err, "failed to list networks")
  143. var networks []string
  144. for _, network := range networkList {
  145. networks = append(networks, network.ID)
  146. }
  147. return networks
  148. }
  149. // ProtectPlugin adds the specified plugin(s) to be protected in case of clean
  150. func (e *Execution) ProtectPlugin(t testing.TB, plugins ...string) {
  151. t.Helper()
  152. for _, plugin := range plugins {
  153. e.protectedElements.plugins[plugin] = struct{}{}
  154. }
  155. }
  156. // ProtectPlugins protects existing plugins from being cleaned up at the end of
  157. // test runs
  158. func ProtectPlugins(ctx context.Context, t testing.TB, testEnv *Execution) {
  159. t.Helper()
  160. plugins := getExistingPlugins(ctx, t, testEnv)
  161. testEnv.ProtectPlugin(t, plugins...)
  162. }
  163. func getExistingPlugins(ctx context.Context, t testing.TB, testEnv *Execution) []string {
  164. t.Helper()
  165. client := testEnv.APIClient()
  166. pluginList, err := client.PluginList(ctx, filters.Args{})
  167. // Docker EE does not allow cluster-wide plugin management.
  168. if errdefs.IsNotImplemented(err) {
  169. return []string{}
  170. }
  171. assert.NilError(t, err, "failed to list plugins")
  172. var plugins []string
  173. for _, plugin := range pluginList {
  174. plugins = append(plugins, plugin.Name)
  175. }
  176. return plugins
  177. }
  178. // ProtectVolume adds the specified volume(s) to be protected in case of clean
  179. func (e *Execution) ProtectVolume(t testing.TB, volumes ...string) {
  180. t.Helper()
  181. for _, vol := range volumes {
  182. e.protectedElements.volumes[vol] = struct{}{}
  183. }
  184. }
  185. // ProtectVolumes protects existing volumes from being cleaned up at the end of
  186. // test runs
  187. func ProtectVolumes(ctx context.Context, t testing.TB, testEnv *Execution) {
  188. t.Helper()
  189. volumes := getExistingVolumes(ctx, t, testEnv)
  190. testEnv.ProtectVolume(t, volumes...)
  191. }
  192. func getExistingVolumes(ctx context.Context, t testing.TB, testEnv *Execution) []string {
  193. t.Helper()
  194. client := testEnv.APIClient()
  195. volumeList, err := client.VolumeList(ctx, volume.ListOptions{})
  196. assert.NilError(t, err, "failed to list volumes")
  197. var volumes []string
  198. for _, vol := range volumeList.Volumes {
  199. volumes = append(volumes, vol.Name)
  200. }
  201. return volumes
  202. }