Skip to content

Commit a3955e3

Browse files
committed
tests/integration: Add tests
Commit adds integration tests for #1298 - Feat pull images before teardown containers on up command (ef9785a) Signed-off-by: Monika Kairaityte <monika@kibit.lt>
1 parent cca5495 commit a3955e3

6 files changed

Lines changed: 335 additions & 1 deletion
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FROM alpine:latest
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
test:
3+
build:
4+
context: .
5+
dockerfile: Dockerfile
6+
image: localhost/not-exists
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
services:
3+
test:
4+
image: alpine:latest
5+
command: ["true"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
test:
3+
build:
4+
context: .
5+
dockerfile: Dockerfile
6+
image: localhost/test-image:1
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
test:
3+
image: alpine:latest
4+
command: ["true"]
5+
# default pull_policy is 'missing'
6+
test-override:
7+
image: alpine:latest
8+
command: ["true"]
9+
pull_policy: always

tests/integration/compose_up_behavior/test_compose_up_behavior.py

Lines changed: 308 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import unittest
66
from typing import Any
77

8+
from packaging import version
89
from parameterized import parameterized
910

1011
from tests.integration.test_utils import RunSubprocessMixin
12+
from tests.integration.test_utils import get_podman_version
1113
from tests.integration.test_utils import podman_compose_path
1214
from tests.integration.test_utils import test_path
1315

@@ -18,7 +20,7 @@ def compose_yaml_path(scenario: str) -> str:
1820
)
1921

2022

21-
class TestComposeDownBehavior(unittest.TestCase, RunSubprocessMixin):
23+
class TestComposeUpBehavior(unittest.TestCase, RunSubprocessMixin):
2224
def get_existing_containers(self, scenario: str) -> dict[str, Any]:
2325
out, _ = self.run_subprocess_assert_returncode(
2426
[
@@ -133,3 +135,308 @@ def test_recreate_on_config_changed(
133135
"-t",
134136
"0",
135137
])
138+
139+
@unittest.skipIf(
140+
get_podman_version() < version.parse("5.6.0"),
141+
"The image pull policy feature was only added as of Podman 5.6.0.",
142+
)
143+
def test_pull_only_if_image_missing(self) -> None:
144+
"""Verify image is pulled, default pull policy is --missing"""
145+
146+
compose_file = compose_yaml_path("default_pull_policy")
147+
image = "docker.io/library/alpine:latest"
148+
149+
try:
150+
self.run_subprocess_assert_returncode([
151+
podman_compose_path(),
152+
"-f",
153+
compose_file,
154+
"up",
155+
"-d",
156+
])
157+
158+
# Remove image while container is still up
159+
self.run_subprocess_assert_returncode([
160+
"podman",
161+
"rmi",
162+
"-f",
163+
image,
164+
])
165+
166+
# up container one more time. Before the teardown of old container,
167+
# podman-compose finds out that the image is missing and pulls
168+
# it again so that it is immediatelly available for a new container
169+
# --missing is default pull policy
170+
_, error = self.run_subprocess_assert_returncode([
171+
podman_compose_path(),
172+
"--verbose",
173+
"-f",
174+
compose_file,
175+
"up",
176+
"-d",
177+
])
178+
179+
out = error.decode('utf-8')
180+
result = '\n'.join(out.splitlines())
181+
# podman pull command now happens before container teardown
182+
self.assertIn("podman pull --policy missing alpine:latest", result)
183+
184+
output, _ = self.run_subprocess_assert_returncode([
185+
podman_compose_path(),
186+
"-f",
187+
compose_file,
188+
"ps",
189+
])
190+
self.assertIn(b"compose_up_behavior_test_1", output)
191+
192+
finally:
193+
self.run_subprocess_assert_returncode([
194+
podman_compose_path(),
195+
"-f",
196+
compose_file,
197+
"down",
198+
"-t",
199+
"0",
200+
])
201+
self.run_subprocess_assert_returncode([
202+
"podman",
203+
"rmi",
204+
"-f",
205+
image,
206+
])
207+
208+
@unittest.skipIf(
209+
get_podman_version() < version.parse("5.6.0"),
210+
"The image pull policy feature was only added as of Podman 5.6.0.",
211+
)
212+
def test_pull_default_policy_overrides_lower_priority_policy(self) -> None:
213+
"""Verify pull policy flag with higher priority overrides the default pull
214+
policy (--missing)"""
215+
216+
compose_file = compose_yaml_path("override_missing")
217+
image = "docker.io/library/alpine:latest"
218+
219+
try:
220+
self.run_subprocess_assert_returncode([
221+
podman_compose_path(),
222+
"-f",
223+
compose_file,
224+
"up",
225+
"-d",
226+
])
227+
228+
# Remove image while container is still up
229+
self.run_subprocess_assert_returncode([
230+
"podman",
231+
"rmi",
232+
"-f",
233+
image,
234+
])
235+
236+
# up container one more time. Before the teardown of old container,
237+
# podman-compose finds out that the pull policy of one of the images
238+
# is --always and pulls the image so that it is immediatelly available
239+
# for a new container
240+
_, error = self.run_subprocess_assert_returncode([
241+
podman_compose_path(),
242+
"--verbose",
243+
"-f",
244+
compose_file,
245+
"up",
246+
"-d",
247+
])
248+
out = error.decode('utf-8')
249+
result = '\n'.join(out.splitlines())
250+
251+
# default pull-policy is --missing, but it has been overriden by policy of
252+
# higher priority (--always)
253+
self.assertIn("podman pull --policy always alpine:latest", result)
254+
255+
finally:
256+
self.run_subprocess_assert_returncode([
257+
podman_compose_path(),
258+
"-f",
259+
compose_file,
260+
"down",
261+
"-t",
262+
"0",
263+
])
264+
self.run_subprocess_assert_returncode([
265+
"podman",
266+
"rmi",
267+
"-f",
268+
image,
269+
])
270+
271+
@unittest.skipIf(
272+
get_podman_version() < version.parse("5.6.0"),
273+
"The image pull policy feature was only added as of Podman 5.6.0.",
274+
)
275+
def test_localhost_image_not_pulled(self) -> None:
276+
"""Verify existing localhost/ images are used locally without pulling"""
277+
278+
compose_file = compose_yaml_path("non_existent_localhost_image")
279+
image = "localhost/test-image:1"
280+
281+
try:
282+
# pre-create image locally by tagging a minimal base image,
283+
# this simulates the image already existing locally
284+
self.run_subprocess_assert_returncode(["podman", "pull", "alpine:latest"])
285+
self.run_subprocess_assert_returncode(["podman", "tag", "alpine:latest", image])
286+
287+
output, _ = self.run_subprocess_assert_returncode([
288+
podman_compose_path(),
289+
"-f",
290+
compose_file,
291+
"up",
292+
"-d",
293+
])
294+
295+
# Remove image while container is still up
296+
self.run_subprocess_assert_returncode([
297+
"podman",
298+
"rmi",
299+
"-f",
300+
image,
301+
])
302+
303+
# since the compose file has both 'build' and 'image',
304+
# podman-compose should use the existing image (or rebuild if
305+
# necessary), but it should never attempt to pull localhost/ images
306+
_, error = self.run_subprocess_assert_returncode([
307+
podman_compose_path(),
308+
"--verbose",
309+
"-f",
310+
compose_file,
311+
"up",
312+
"-d",
313+
])
314+
out = error.decode('utf-8')
315+
result = '\n'.join(out.splitlines())
316+
self.assertNotIn("Trying to pull", result)
317+
318+
# Confirm container is actually started with localhost/ image
319+
output, _ = self.run_subprocess_assert_returncode([
320+
"podman",
321+
"ps",
322+
"-a",
323+
"--filter",
324+
f"ancestor={image}",
325+
"--format",
326+
"{{.Image}}",
327+
])
328+
out = output.decode('utf-8')
329+
result = '\n'.join(out.splitlines())
330+
self.assertIn(image, result)
331+
332+
finally:
333+
self.run_subprocess_assert_returncode([
334+
podman_compose_path(),
335+
"-f",
336+
compose_file,
337+
"down",
338+
])
339+
self.run_subprocess_assert_returncode([
340+
"podman",
341+
"rmi",
342+
"-f",
343+
image,
344+
])
345+
346+
@unittest.skipIf(
347+
get_podman_version() < version.parse("5.6.0"),
348+
"The image pull policy feature was only added as of Podman 5.6.0.",
349+
)
350+
def test_localhost_image_built_if_does_not_exist(self) -> None:
351+
"""Verify non-existent localhost/ images are built instead of pulling"""
352+
353+
compose_file = compose_yaml_path("build_localhost_image")
354+
image = "localhost/not-exists"
355+
356+
# check image does not exist
357+
self.run_subprocess_assert_returncode(
358+
[
359+
"podman",
360+
"image",
361+
"exists",
362+
image,
363+
],
364+
1,
365+
)
366+
367+
try:
368+
# Verify image is missing before start
369+
output, _ = self.run_subprocess_assert_returncode(
370+
["podman", "images", "--filter", f"reference={image}", "--format", "{{.Id}}"],
371+
)
372+
out = output.decode('utf-8')
373+
result = '\n'.join(out.splitlines())
374+
self.assertEqual(result, "")
375+
376+
output, _ = self.run_subprocess_assert_returncode([
377+
podman_compose_path(),
378+
"-f",
379+
compose_file,
380+
"up",
381+
"-d",
382+
])
383+
384+
# Remove image while container is still up
385+
self.run_subprocess_assert_returncode([
386+
"podman",
387+
"rmi",
388+
"-f",
389+
image,
390+
])
391+
392+
# image was not pulled, it was built
393+
output, _ = self.run_subprocess_assert_returncode([
394+
podman_compose_path(),
395+
"--verbose",
396+
"-f",
397+
compose_file,
398+
"up",
399+
"-d",
400+
])
401+
402+
out = output.decode('utf-8')
403+
result = '\n'.join(out.splitlines())
404+
self.assertNotIn("Trying to pull", result)
405+
406+
# After up command, image now exists (was built)
407+
output, _ = self.run_subprocess_assert_returncode([
408+
"podman",
409+
"images",
410+
"--filter",
411+
f"reference={image}",
412+
"--format",
413+
"{{.Repository}}:{{.Tag}}",
414+
])
415+
out = output.decode('utf-8')
416+
result = '\n'.join(out.splitlines())
417+
self.assertIn(image, result)
418+
419+
# Container uses the locally built image
420+
output, _ = self.run_subprocess_assert_returncode([
421+
"podman",
422+
"ps",
423+
"-a",
424+
"--filter",
425+
f"ancestor={image}",
426+
"--format",
427+
"{{.Image}}",
428+
])
429+
out = output.decode('utf-8')
430+
result = '\n'.join(out.splitlines())
431+
self.assertIn(image, result)
432+
433+
finally:
434+
self.run_subprocess_assert_returncode([
435+
podman_compose_path(),
436+
"-f",
437+
compose_file,
438+
"down",
439+
])
440+
self.run_subprocess_assert_returncode(
441+
["podman", "rmi", "-f", image],
442+
)

0 commit comments

Comments
 (0)