1package environment // import "github.com/docker/docker/internal/test/environment"
2
3import (
4	"context"
5
6	"github.com/docker/docker/api/types"
7	"github.com/docker/docker/api/types/filters"
8	dclient "github.com/docker/docker/client"
9	"github.com/docker/docker/internal/test"
10	"gotest.tools/assert"
11)
12
13var frozenImages = []string{"busybox:latest", "busybox:glibc", "hello-world:frozen", "debian:jessie"}
14
15type 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
23func newProtectedElements() protectedElements {
24	return protectedElements{
25		containers: map[string]struct{}{},
26		images:     map[string]struct{}{},
27		networks:   map[string]struct{}{},
28		plugins:    map[string]struct{}{},
29		volumes:    map[string]struct{}{},
30	}
31}
32
33// ProtectAll protects the existing environment (containers, images, networks,
34// volumes, and, on Linux, plugins) from being cleaned up at the end of test
35// runs
36func ProtectAll(t assert.TestingT, testEnv *Execution) {
37	if ht, ok := t.(test.HelperT); ok {
38		ht.Helper()
39	}
40	ProtectContainers(t, testEnv)
41	ProtectImages(t, testEnv)
42	ProtectNetworks(t, testEnv)
43	ProtectVolumes(t, testEnv)
44	if testEnv.OSType == "linux" {
45		ProtectPlugins(t, testEnv)
46	}
47}
48
49// ProtectContainer adds the specified container(s) to be protected in case of
50// clean
51func (e *Execution) ProtectContainer(t assert.TestingT, containers ...string) {
52	if ht, ok := t.(test.HelperT); ok {
53		ht.Helper()
54	}
55	for _, container := range containers {
56		e.protectedElements.containers[container] = struct{}{}
57	}
58}
59
60// ProtectContainers protects existing containers from being cleaned up at the
61// end of test runs
62func ProtectContainers(t assert.TestingT, testEnv *Execution) {
63	if ht, ok := t.(test.HelperT); ok {
64		ht.Helper()
65	}
66	containers := getExistingContainers(t, testEnv)
67	testEnv.ProtectContainer(t, containers...)
68}
69
70func getExistingContainers(t assert.TestingT, testEnv *Execution) []string {
71	if ht, ok := t.(test.HelperT); ok {
72		ht.Helper()
73	}
74	client := testEnv.APIClient()
75	containerList, err := client.ContainerList(context.Background(), types.ContainerListOptions{
76		All: true,
77	})
78	assert.NilError(t, err, "failed to list containers")
79
80	var containers []string
81	for _, container := range containerList {
82		containers = append(containers, container.ID)
83	}
84	return containers
85}
86
87// ProtectImage adds the specified image(s) to be protected in case of clean
88func (e *Execution) ProtectImage(t assert.TestingT, images ...string) {
89	if ht, ok := t.(test.HelperT); ok {
90		ht.Helper()
91	}
92	for _, image := range images {
93		e.protectedElements.images[image] = struct{}{}
94	}
95}
96
97// ProtectImages protects existing images and on linux frozen images from being
98// cleaned up at the end of test runs
99func ProtectImages(t assert.TestingT, testEnv *Execution) {
100	if ht, ok := t.(test.HelperT); ok {
101		ht.Helper()
102	}
103	images := getExistingImages(t, testEnv)
104
105	if testEnv.OSType == "linux" {
106		images = append(images, frozenImages...)
107	}
108	testEnv.ProtectImage(t, images...)
109}
110
111func getExistingImages(t assert.TestingT, testEnv *Execution) []string {
112	if ht, ok := t.(test.HelperT); ok {
113		ht.Helper()
114	}
115	client := testEnv.APIClient()
116	filter := filters.NewArgs()
117	filter.Add("dangling", "false")
118	imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
119		All:     true,
120		Filters: filter,
121	})
122	assert.NilError(t, err, "failed to list images")
123
124	var images []string
125	for _, image := range imageList {
126		images = append(images, tagsFromImageSummary(image)...)
127	}
128	return images
129}
130
131func tagsFromImageSummary(image types.ImageSummary) []string {
132	var result []string
133	for _, tag := range image.RepoTags {
134		if tag != "<none>:<none>" {
135			result = append(result, tag)
136		}
137	}
138	for _, digest := range image.RepoDigests {
139		if digest != "<none>@<none>" {
140			result = append(result, digest)
141		}
142	}
143	return result
144}
145
146// ProtectNetwork adds the specified network(s) to be protected in case of
147// clean
148func (e *Execution) ProtectNetwork(t assert.TestingT, networks ...string) {
149	if ht, ok := t.(test.HelperT); ok {
150		ht.Helper()
151	}
152	for _, network := range networks {
153		e.protectedElements.networks[network] = struct{}{}
154	}
155}
156
157// ProtectNetworks protects existing networks from being cleaned up at the end
158// of test runs
159func ProtectNetworks(t assert.TestingT, testEnv *Execution) {
160	if ht, ok := t.(test.HelperT); ok {
161		ht.Helper()
162	}
163	networks := getExistingNetworks(t, testEnv)
164	testEnv.ProtectNetwork(t, networks...)
165}
166
167func getExistingNetworks(t assert.TestingT, testEnv *Execution) []string {
168	if ht, ok := t.(test.HelperT); ok {
169		ht.Helper()
170	}
171	client := testEnv.APIClient()
172	networkList, err := client.NetworkList(context.Background(), types.NetworkListOptions{})
173	assert.NilError(t, err, "failed to list networks")
174
175	var networks []string
176	for _, network := range networkList {
177		networks = append(networks, network.ID)
178	}
179	return networks
180}
181
182// ProtectPlugin adds the specified plugin(s) to be protected in case of clean
183func (e *Execution) ProtectPlugin(t assert.TestingT, plugins ...string) {
184	if ht, ok := t.(test.HelperT); ok {
185		ht.Helper()
186	}
187	for _, plugin := range plugins {
188		e.protectedElements.plugins[plugin] = struct{}{}
189	}
190}
191
192// ProtectPlugins protects existing plugins from being cleaned up at the end of
193// test runs
194func ProtectPlugins(t assert.TestingT, testEnv *Execution) {
195	if ht, ok := t.(test.HelperT); ok {
196		ht.Helper()
197	}
198	plugins := getExistingPlugins(t, testEnv)
199	testEnv.ProtectPlugin(t, plugins...)
200}
201
202func getExistingPlugins(t assert.TestingT, testEnv *Execution) []string {
203	if ht, ok := t.(test.HelperT); ok {
204		ht.Helper()
205	}
206	client := testEnv.APIClient()
207	pluginList, err := client.PluginList(context.Background(), filters.Args{})
208	// Docker EE does not allow cluster-wide plugin management.
209	if dclient.IsErrNotImplemented(err) {
210		return []string{}
211	}
212	assert.NilError(t, err, "failed to list plugins")
213
214	var plugins []string
215	for _, plugin := range pluginList {
216		plugins = append(plugins, plugin.Name)
217	}
218	return plugins
219}
220
221// ProtectVolume adds the specified volume(s) to be protected in case of clean
222func (e *Execution) ProtectVolume(t assert.TestingT, volumes ...string) {
223	if ht, ok := t.(test.HelperT); ok {
224		ht.Helper()
225	}
226	for _, volume := range volumes {
227		e.protectedElements.volumes[volume] = struct{}{}
228	}
229}
230
231// ProtectVolumes protects existing volumes from being cleaned up at the end of
232// test runs
233func ProtectVolumes(t assert.TestingT, testEnv *Execution) {
234	if ht, ok := t.(test.HelperT); ok {
235		ht.Helper()
236	}
237	volumes := getExistingVolumes(t, testEnv)
238	testEnv.ProtectVolume(t, volumes...)
239}
240
241func getExistingVolumes(t assert.TestingT, testEnv *Execution) []string {
242	if ht, ok := t.(test.HelperT); ok {
243		ht.Helper()
244	}
245	client := testEnv.APIClient()
246	volumeList, err := client.VolumeList(context.Background(), filters.Args{})
247	assert.NilError(t, err, "failed to list volumes")
248
249	var volumes []string
250	for _, volume := range volumeList.Volumes {
251		volumes = append(volumes, volume.Name)
252	}
253	return volumes
254}
255