1- import asyncio
21import os
32import shutil
43import subprocess
54import tempfile
65import typing
76from pathlib import Path , PurePath
87from string import Template
9- from typing import ClassVar , Optional , Protocol , cast
8+ from typing import TYPE_CHECKING , ClassVar , Optional , Protocol , cast
109
1110import aiofiles
1211import click
1312
1413from flyte import Secret
14+
1515from flyte ._code_bundle ._ignore import STANDARD_IGNORE_PATTERNS
1616from flyte ._image import (
1717 AptPackages ,
4545 get_uv_editable_install_mounts ,
4646)
4747from flyte ._logging import logger
48+ from flyte ._utils .asyncify import run_sync_with_loop
49+
50+ if TYPE_CHECKING :
51+ from flyte ._build import ImageBuild
4852
4953_F_IMG_ID = "_F_IMG_ID"
5054FLYTE_DOCKER_BUILDER_CACHE_FROM = "FLYTE_DOCKER_BUILDER_CACHE_FROM"
@@ -558,22 +562,26 @@ def get_checkers(self) -> Optional[typing.List[typing.Type[ImageChecker]]]:
558562 # Can get a public token for docker.io but ghcr requires a pat, so harder to get the manifest anonymously
559563 return [LocalDockerCommandImageChecker , LocalPodmanCommandImageChecker , DockerAPIImageChecker ]
560564
561- async def build_image (self , image : Image , dry_run : bool = False ) -> str :
565+ async def build_image (self , image : Image , dry_run : bool = False , wait : bool = True ) -> "ImageBuild" :
566+ from flyte ._build import ImageBuild
567+
562568 if image .dockerfile :
563569 # If a dockerfile is provided, use it directly
564- return await self ._build_from_dockerfile (image , push = True )
570+ uri = await self ._build_from_dockerfile (image , push = True , wait = wait )
571+ return ImageBuild (uri = uri , remote_run = None )
565572
566573 if len (image ._layers ) == 0 :
567574 logger .warning ("No layers to build, returning the image URI as is." )
568- return image .uri
575+ return ImageBuild ( uri = image .uri , remote_run = None )
569576
570- return await self ._build_image (
577+ uri = await self ._build_image (
571578 image ,
572579 push = True ,
573580 dry_run = dry_run ,
574581 )
582+ return ImageBuild (uri = uri , remote_run = None )
575583
576- async def _build_from_dockerfile (self , image : Image , push : bool ) -> str :
584+ async def _build_from_dockerfile (self , image : Image , push : bool , wait : bool = True ) -> str :
577585 """
578586 Build the image from a provided Dockerfile.
579587 """
@@ -606,7 +614,10 @@ async def _build_from_dockerfile(self, image: Image, push: bool) -> str:
606614 logger .debug (f"Build command: { concat_command } " )
607615 click .secho (f"Run command: { concat_command } " , fg = "blue" )
608616
609- await asyncio .to_thread (subprocess .run , command , cwd = str (cast (Path , image .dockerfile ).cwd ()), check = True )
617+ if wait :
618+ await run_sync_with_loop (subprocess .run , command , cwd = str (cast (Path , image .dockerfile ).cwd ()), check = True )
619+ else :
620+ await run_sync_with_loop (subprocess .Popen , command , cwd = str (cast (Path , image .dockerfile ).cwd ()))
610621
611622 return image .uri
612623
@@ -615,14 +626,14 @@ async def _ensure_buildx_builder():
615626 """Ensure there is a docker buildx builder called flyte"""
616627 # Check if buildx is available
617628 try :
618- await asyncio . to_thread (
629+ await run_sync_with_loop (
619630 subprocess .run , ["docker" , "buildx" , "version" ], check = True , stdout = subprocess .DEVNULL
620631 )
621632 except subprocess .CalledProcessError :
622633 raise RuntimeError ("Docker buildx is not available. Make sure BuildKit is installed and enabled." )
623634
624635 # List builders
625- result = await asyncio . to_thread (
636+ result = await run_sync_with_loop (
626637 subprocess .run , ["docker" , "buildx" , "ls" ], capture_output = True , text = True , check = True
627638 )
628639 builders = result .stdout
@@ -631,7 +642,7 @@ async def _ensure_buildx_builder():
631642 if DockerImageBuilder ._builder_name not in builders :
632643 # No default builder found, create one
633644 logger .info ("No buildx builder found, creating one..." )
634- await asyncio . to_thread (
645+ await run_sync_with_loop (
635646 subprocess .run ,
636647 [
637648 "docker" ,
@@ -647,7 +658,7 @@ async def _ensure_buildx_builder():
647658 else :
648659 logger .info ("Buildx builder already exists." )
649660
650- async def _build_image (self , image : Image , * , push : bool = True , dry_run : bool = False ) -> str :
661+ async def _build_image (self , image : Image , * , push : bool = True , dry_run : bool = False , wait : bool = True ) -> str :
651662 """
652663 if default image (only base image and locked), raise an error, don't have a dockerfile
653664 if dockerfile, just build
@@ -729,7 +740,10 @@ async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool =
729740 click .secho (f"Run command: { concat_command } " , fg = "blue" )
730741
731742 try :
732- await asyncio .to_thread (subprocess .run , command , check = True )
743+ if wait :
744+ await run_sync_with_loop (subprocess .run , command , check = True )
745+ else :
746+ await run_sync_with_loop (subprocess .Popen , command )
733747 except subprocess .CalledProcessError as e :
734748 logger .error (f"Failed to build image: { e } " )
735749 raise RuntimeError (f"Failed to build image: { e } " )
0 commit comments