Skip to content

Commit c99ecbd

Browse files
Merge pull request #559 from fulminemizzega/main
fix image build not using cache
2 parents 53ab83f + 781def8 commit c99ecbd

File tree

4 files changed

+106
-41
lines changed

4 files changed

+106
-41
lines changed

podman/domain/images_build.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ def build(self, **kwargs) -> tuple[Image, Iterator[bytes]]:
6363
isolation (str) – Isolation technology used during build. (ignored)
6464
use_config_proxy (bool) – (ignored)
6565
http_proxy (bool) - Inject http proxy environment variables into container (Podman only)
66-
layers (bool) - Cache intermediate layers during build.
66+
layers (bool) - Cache intermediate layers during build. Default True.
6767
output (str) - specifies if any custom build output is selected for following build.
6868
outputformat (str) - The format of the output image's manifest and configuration data.
69+
Default to "application/vnd.oci.image.manifest.v1+json" (OCI format).
6970
manifest (str) - add the image to the specified manifest list.
7071
Creates manifest list if it does not exist.
7172
secrets (list[str]) - Secret files/envs to expose to the build
@@ -172,7 +173,7 @@ def _render_params(kwargs) -> dict[str, list[Any]]:
172173
raise PodmanError("Custom encoding not supported when gzip enabled.")
173174

174175
params = {
175-
"dockerfile": kwargs.get("dockerfile"),
176+
"dockerfile": kwargs.get("dockerfile", f".containerfile.{random.getrandbits(160):x}"),
176177
"forcerm": kwargs.get("forcerm"),
177178
"httpproxy": kwargs.get("http_proxy"),
178179
"networkmode": kwargs.get("network_mode"),
@@ -187,9 +188,11 @@ def _render_params(kwargs) -> dict[str, list[Any]]:
187188
"squash": kwargs.get("squash"),
188189
"t": kwargs.get("tag"),
189190
"target": kwargs.get("target"),
190-
"layers": kwargs.get("layers"),
191+
"layers": kwargs.get("layers", True),
191192
"output": kwargs.get("output"),
192-
"outputformat": kwargs.get("outputformat"),
193+
"outputformat": kwargs.get(
194+
"outputformat", "application/vnd.oci.image.manifest.v1+json"
195+
),
193196
}
194197

195198
if "buildargs" in kwargs:
@@ -213,8 +216,5 @@ def _render_params(kwargs) -> dict[str, list[Any]]:
213216
if "secrets" in kwargs:
214217
params["secrets"] = json.dumps(kwargs.get("secrets"))
215218

216-
if params["dockerfile"] is None:
217-
params["dockerfile"] = f".containerfile.{random.getrandbits(160):x}"
218-
219219
# Remove any unset parameters
220220
return dict(filter(lambda i: i[1] is not None, params.items()))

podman/tests/integration/test_containers.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import io
22
import random
33
import tarfile
4-
import tempfile
54
import unittest
65

76
try:
@@ -86,7 +85,7 @@ def test_container_crud(self):
8685
with self.subTest("Archive /root/unittest"):
8786
self.assertTrue(container.put_archive("/root", data=tarball))
8887

89-
actual, stats = container.get_archive("/root")
88+
actual, _ = container.get_archive("/root")
9089

9190
with io.BytesIO() as fd:
9291
for chunk in actual:
@@ -184,16 +183,8 @@ def test_container_commit(self):
184183

185184
def test_container_rm_anonymous_volume(self):
186185
with self.subTest("Check anonymous volume is removed"):
187-
container_file = """
188-
FROM alpine
189-
VOLUME myvol
190-
ENV foo=bar
191-
"""
192-
tmp_file = tempfile.mktemp()
193-
file = open(tmp_file, 'w')
194-
file.write(container_file)
195-
file.close()
196-
self.client.images.build(dockerfile=tmp_file, tag="test-img", path=".")
186+
container_file = io.StringIO("\n".join(["FROM alpine", "VOLUME myvol", "ENV foo=bar"]))
187+
test_img, _ = self.client.images.build(fileobj=container_file, tag="test-img", path=".")
197188

198189
# get existing number of containers and volumes
199190
existing_containers = self.client.containers.list(all=True)
@@ -211,6 +202,8 @@ def test_container_rm_anonymous_volume(self):
211202
self.assertEqual(len(container_list), len(existing_containers))
212203
volume_list = self.client.volumes.list()
213204
self.assertEqual(len(volume_list), len(existing_volumes))
205+
# clean up
206+
self.client.images.remove(test_img)
214207

215208
def test_container_labels(self):
216209
labels = {'label1': 'value1', 'label2': 'value2'}

podman/tests/integration/test_images.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
import io
1818
import os
19+
import json
1920
import platform
2021
import tarfile
2122
import tempfile
2223
import types
2324
import unittest
25+
import random
2426

2527
import podman.tests.integration.base as base
2628
from podman import PodmanClient
@@ -145,12 +147,48 @@ def test_corrupt_load(self):
145147
self.assertIn("payload does not match", e.exception.explanation)
146148

147149
def test_build(self):
148-
buffer = io.StringIO("""FROM quay.io/libpod/alpine_labels:latest""")
149-
150-
image, stream = self.client.images.build(fileobj=buffer)
150+
buffer = io.StringIO("""FROM scratch""")
151+
image, _ = self.client.images.build(fileobj=buffer)
151152
self.assertIsNotNone(image)
152153
self.assertIsNotNone(image.id)
153154

155+
def test_build_cache(self):
156+
"""Check build caching when enabled
157+
158+
Build twice with caching enabled (default), then again with nocache
159+
"""
160+
161+
def look_for_cache(stream) -> bool:
162+
# Search for a line with contents "-> Using cache <image id>"
163+
uses_cache = False
164+
for line in stream:
165+
parsed = json.loads(line)['stream']
166+
if "Using cache" in parsed:
167+
uses_cache = True
168+
break
169+
return uses_cache
170+
171+
label = str(random.getrandbits(32))
172+
buffer = io.StringIO(f"""FROM scratch\nLABEL test={label}""")
173+
image, _ = self.client.images.build(fileobj=buffer)
174+
buffer.seek(0)
175+
cached_image, stream = self.client.images.build(fileobj=buffer)
176+
self.assertTrue(look_for_cache(stream))
177+
self.assertEqual(
178+
cached_image.id,
179+
image.id,
180+
msg="Building twice with cache does not produce the same image id",
181+
)
182+
# Build again with disabled cache
183+
buffer.seek(0)
184+
uncached_image, stream = self.client.images.build(fileobj=buffer, nocache=True)
185+
self.assertFalse(look_for_cache(stream))
186+
self.assertNotEqual(
187+
uncached_image.id,
188+
image.id,
189+
msg="Building twice without cache produces the same image id",
190+
)
191+
154192
def test_build_with_manifest(self):
155193
buffer = io.StringIO("""FROM quay.io/libpod/alpine_labels:latest""")
156194

@@ -190,7 +228,7 @@ def add_file(name: str, content: str):
190228
# If requesting a custom context, currently must specify the dockerfile name
191229
self.client.images.build(custom_context=True, fileobj=context)
192230

193-
image, stream = self.client.images.build(
231+
image, _ = self.client.images.build(
194232
fileobj=context,
195233
dockerfile="MyDockerfile",
196234
custom_context=True,

podman/tests/unit/test_build.py

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io
2+
import requests
23
import json
34
import unittest
45

@@ -16,6 +17,20 @@
1617
from podman.domain.images import Image
1718
from podman.errors import BuildError, DockerException
1819

20+
good_image_id = "032b8b2855fc"
21+
good_stream = [
22+
{"stream": " ---\u003e a9eb17255234"},
23+
{"stream": "Step 1 : VOLUME /data"},
24+
{"stream": " ---\u003e Running in abdc1e6896c6"},
25+
{"stream": " ---\u003e 713bca62012e"},
26+
{"stream": "Removing intermediate container abdc1e6896c6"},
27+
{"stream": "Step 2 : CMD [\"/bin/sh\"]"},
28+
{"stream": " ---\u003e Running in dba30f2a1a7e"},
29+
{"stream": " ---\u003e 032b8b2855fc"},
30+
{"stream": "Removing intermediate container dba30f2a1a7e"},
31+
{"stream": f"{good_image_id}\n"},
32+
]
33+
1934

2035
class TestBuildCase(unittest.TestCase):
2136
"""Test ImagesManager build().
@@ -41,19 +56,7 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar):
4156
mock_prepare_containerfile.return_value = "Containerfile"
4257
mock_create_tar.return_value = b"This is a mocked tarball."
4358

44-
stream = [
45-
{"stream": " ---\u003e a9eb17255234"},
46-
{"stream": "Step 1 : VOLUME /data"},
47-
{"stream": " ---\u003e Running in abdc1e6896c6"},
48-
{"stream": " ---\u003e 713bca62012e"},
49-
{"stream": "Removing intermediate container abdc1e6896c6"},
50-
{"stream": "Step 2 : CMD [\"/bin/sh\"]"},
51-
{"stream": " ---\u003e Running in dba30f2a1a7e"},
52-
{"stream": " ---\u003e 032b8b2855fc"},
53-
{"stream": "Removing intermediate container dba30f2a1a7e"},
54-
{"stream": "032b8b2855fc\n"},
55-
]
56-
59+
stream = good_stream
5760
buffer = io.StringIO()
5861
for entry in stream:
5962
buffer.write(json.JSONEncoder().encode(entry))
@@ -72,9 +75,9 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar):
7275
text=buffer.getvalue(),
7376
)
7477
mock.get(
75-
tests.LIBPOD_URL + "/images/032b8b2855fc/json",
78+
tests.LIBPOD_URL + f"/images/{good_image_id}/json",
7679
json={
77-
"Id": "032b8b2855fc",
80+
"Id": good_image_id,
7881
"ParentId": "",
7982
"RepoTags": ["fedora:latest", "fedora:33", "<none>:<none>"],
8083
"RepoDigests": [
@@ -104,7 +107,7 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar):
104107
secrets=["id=example,src=podman-build-secret123"],
105108
)
106109
self.assertIsInstance(image, Image)
107-
self.assertEqual(image.id, "032b8b2855fc")
110+
self.assertEqual(image.id, good_image_id)
108111
self.assertIsInstance(logs, Iterable)
109112

110113
@patch.object(api, "create_tar")
@@ -134,16 +137,47 @@ def test_build_logged_error(self, mock_prepare_containerfile, mock_create_tar):
134137

135138
@requests_mock.Mocker()
136139
def test_build_no_context(self, mock):
137-
mock.post(tests.LIBPOD_URL + "/images/build")
140+
mock.post(tests.LIBPOD_URL + "/build")
138141
with self.assertRaises(TypeError):
139142
self.client.images.build()
140143

141144
@requests_mock.Mocker()
142145
def test_build_encoding(self, mock):
143-
mock.post(tests.LIBPOD_URL + "/images/build")
146+
mock.post(tests.LIBPOD_URL + "/build")
144147
with self.assertRaises(DockerException):
145148
self.client.images.build(path="/root", gzip=True, encoding="utf-8")
146149

150+
@patch.object(api, "create_tar")
151+
@patch.object(api, "prepare_containerfile")
152+
def test_build_defaults(self, mock_prepare_containerfile, mock_create_tar):
153+
"""Check the defaults used by images.build"""
154+
mock_prepare_containerfile.return_value = "Containerfile"
155+
mock_create_tar.return_value = b"This is a mocked tarball."
156+
157+
stream = good_stream
158+
buffer = io.StringIO()
159+
for entry in stream:
160+
buffer.write(json.dumps(entry))
161+
buffer.write("\n")
162+
163+
with requests_mock.Mocker() as mock:
164+
query = "?outputformat=" + (
165+
requests.utils.quote("application/vnd.oci.image.manifest.v1+json", safe='')
166+
+ "&layers=True"
167+
)
168+
mock.post(
169+
tests.LIBPOD_URL + "/build" + query,
170+
text=buffer.getvalue(),
171+
)
172+
mock.get(
173+
tests.LIBPOD_URL + f"/images/{good_image_id}/json",
174+
json={
175+
"Id": "unittest",
176+
},
177+
)
178+
img, _ = self.client.images.build(path="/tmp/context_dir")
179+
assert img.id == "unittest"
180+
147181

148182
if __name__ == '__main__':
149183
unittest.main()

0 commit comments

Comments
 (0)