Skip to content

Commit 43768ec

Browse files
committed
fix: clear error for missing .dockerignore in with_dockerignore
A non-existent path passed to Image.with_dockerignore() surfaced as a raw FileNotFoundError at build time (often because a relative path like '.dockerignore' was resolved against the builder's working directory rather than where the image was defined). Resolve the path to an absolute path at definition time and raise a clear ImageBuildError (a RuntimeUserError) when the file is missing, in both the remote and local docker build paths. fixes FLYTE-SDK-4Z Signed-off-by: Haytham Abuelfutuh <haytham@afutuh.com>
1 parent 3c69976 commit 43768ec

4 files changed

Lines changed: 48 additions & 1 deletion

File tree

src/flyte/_image.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,17 @@ def with_code_bundle(
11801180
return self.clone(addl_layer=CodeBundleLayer(copy_style=copy_style, dst=dst))
11811181

11821182
def with_dockerignore(self, path: Path) -> Image:
1183-
new_image = self.clone(addl_layer=DockerIgnore(path=str(path)))
1183+
import flyte.errors
1184+
1185+
# Resolve to an absolute path so the file can still be located at build time, which may run
1186+
# from a different working directory than where the image was defined (e.g. the remote builder).
1187+
resolved = Path(path).expanduser()
1188+
if not resolved.is_file():
1189+
raise flyte.errors.ImageBuildError(
1190+
f"The .dockerignore file specified via with_dockerignore() was not found at '{path}'. "
1191+
f"Ensure the path points to an existing file."
1192+
)
1193+
new_image = self.clone(addl_layer=DockerIgnore(path=str(resolved.resolve())))
11841194
return new_image
11851195

11861196
def with_uv_project(

src/flyte/_internal/imagebuild/docker_builder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,13 @@ async def handle(
393393
class DockerIgnoreHandler:
394394
@staticmethod
395395
async def handle(layer: DockerIgnore, context_path: Path, _: str):
396+
if not Path(layer.path).is_file():
397+
from flyte.errors import ImageBuildError
398+
399+
raise ImageBuildError(
400+
f"The .dockerignore file specified via with_dockerignore() was not found at '{layer.path}'. "
401+
f"Ensure the path points to an existing file."
402+
)
396403
shutil.copy(layer.path, context_path)
397404

398405

src/flyte/_internal/imagebuild/remote_builder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
402402
)
403403
layers.append(commands_layer)
404404
elif isinstance(layer, DockerIgnore):
405+
if not Path(layer.path).is_file():
406+
raise flyte.errors.ImageBuildError(
407+
f"The .dockerignore file specified via with_dockerignore() was not found at '{layer.path}'. "
408+
f"Ensure the path points to an existing file."
409+
)
405410
shutil.copy(layer.path, context_path)
406411
elif isinstance(layer, CodeBundleLayer):
407412
if layer.root_dir is None:

tests/flyte/test_image.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,31 @@ def test_image_uri_changes_when_dockerignore_content_changes(tmp_path):
601601
assert uri1 != img2.uri
602602

603603

604+
def test_with_dockerignore_missing_file_raises_image_build_error(tmp_path):
605+
"""A non-existent .dockerignore path must raise a clear ImageBuildError, not a raw FileNotFoundError."""
606+
from flyte.errors import ImageBuildError
607+
608+
missing = tmp_path / ".dockerignore" # never created
609+
with pytest.raises(ImageBuildError, match="with_dockerignore"):
610+
Image.from_debian_base(registry="localhost", name="test").with_dockerignore(missing)
611+
612+
613+
def test_with_dockerignore_resolves_to_absolute_path(tmp_path, monkeypatch):
614+
"""with_dockerignore must store an absolute path so the file is locatable from any cwd at build time."""
615+
from flyte._image import DockerIgnore
616+
617+
di_file = tmp_path / ".dockerignore"
618+
di_file.write_text("*.log\n")
619+
620+
# Reference the file via a relative path from tmp_path, then change cwd to simulate the builder.
621+
monkeypatch.chdir(tmp_path)
622+
img = Image.from_debian_base(registry="localhost", name="test").with_dockerignore(Path(".dockerignore"))
623+
624+
layer = next(layer for layer in img._layers if isinstance(layer, DockerIgnore))
625+
assert Path(layer.path).is_absolute()
626+
assert Path(layer.path).is_file()
627+
628+
604629
def test_ids_for_different_python_version():
605630
ex_11 = Image.from_debian_base(python_version=(3, 11), install_flyte=False).with_source_file(Path(__file__))
606631
ex_12 = Image.from_debian_base(python_version=(3, 12), install_flyte=False).with_source_file(Path(__file__))

0 commit comments

Comments
 (0)