6060)
6161from samcli .lib .utils .stream_writer import StreamWriter
6262from samcli .local .docker .container import ContainerContext
63- from samcli .local .docker .exceptions import ContainerArchiveImageLoadFailedException
63+ from samcli .local .docker .container_client import ContainerClient
64+ from samcli .local .docker .exceptions import BuildkitNotAvailableException , ContainerArchiveImageLoadFailedException
65+ from samcli .local .docker .image_build_client import CLIBuildClient , ImageBuildClient , SDKBuildClient
6466from samcli .local .docker .lambda_build_container import LambdaBuildContainer
6567from samcli .local .docker .manager import ContainerManager , DockerImagePullFailedException
6668from samcli .local .docker .utils import (
@@ -106,14 +108,15 @@ def __init__(
106108 parallel : bool = False ,
107109 mode : Optional [str ] = None ,
108110 stream_writer : Optional [StreamWriter ] = None ,
109- docker_client : Optional [docker . DockerClient ] = None ,
111+ container_client : Optional [ContainerClient ] = None ,
110112 container_env_var : Optional [Dict ] = None ,
111113 container_env_var_file : Optional [str ] = None ,
112114 build_images : Optional [Dict ] = None ,
113115 combine_dependencies : bool = True ,
114116 build_in_source : Optional [bool ] = None ,
115117 mount_with_write : bool = False ,
116118 mount_symlinks : Optional [bool ] = False ,
119+ use_buildkit : Optional [bool ] = False ,
117120 ) -> None :
118121 """
119122 Initialize the class
@@ -144,8 +147,8 @@ def __init__(
144147 Optional, name of the build mode to use ex: 'debug'
145148 stream_writer : Optional[StreamWriter]
146149 An optional stream writer to accept stderr output
147- docker_client : Optional[docker.DockerClient ]
148- An optional Docker client object to replace the default one loaded from env
150+ container_client : Optional[ContainerClient ]
151+ An optional container client object to replace the default one loaded from env
149152 container_env_var : Optional[Dict]
150153 An optional dictionary of environment variables to pass to the container
151154 container_env_var_file : Optional[str]
@@ -161,6 +164,8 @@ def __init__(
161164 Mount source code directory with write permissions when building inside container.
162165 mount_symlinks: Optional[bool]
163166 True if symlinks should be mounted in the container.
167+ use_buildkit: Optional[bool]
168+ Optional flag for building Image functions with buildkit support.
164169 """
165170 self ._resources_to_build = resources_to_build
166171 self ._build_dir = build_dir
@@ -175,11 +180,12 @@ def __init__(
175180 self ._mode = mode
176181 self ._stream_writer = stream_writer if stream_writer else StreamWriter (stream = osutils .stderr (), auto_flush = True )
177182
178- # Store docker_client parameter for lazy initialization
179- # Only validate container runtime when Docker client is actually accessed
183+ # Store container_client parameter for lazy initialization
184+ # Only validate container runtime when container client is actually accessed
180185 # This prevents unnecessary validation for builds that don't require containers
181- self ._docker_client_param = docker_client
182- self ._validated_docker_client : Optional [docker .DockerClient ] = None
186+ # NOTE: It seems like at this point container_client is only ever passed in the tests for mocking.
187+ self ._container_client_param = container_client
188+ self ._validated_container_client : Optional [ContainerClient ] = None
183189
184190 self ._deprecated_runtimes = DEPRECATED_RUNTIMES
185191 self ._colored = Colored ()
@@ -190,16 +196,18 @@ def __init__(
190196 self ._build_in_source = build_in_source
191197 self ._mount_with_write = mount_with_write
192198 self ._mount_symlinks = mount_symlinks
199+ self ._use_buildkit = use_buildkit
200+ self ._image_build_client : Optional [ImageBuildClient ] = None
193201
194202 @property
195- def _docker_client (self ) -> docker . DockerClient :
203+ def _container_client (self ) -> ContainerClient :
196204 """
197- Lazy initialization of Docker client. Only validates container runtime when actually accessed.
205+ Lazy initialization of container client. Only validates container runtime when actually accessed.
198206 This prevents unnecessary container runtime validation for builds that don't require containers.
199207 """
200- if self ._validated_docker_client is None :
201- self ._validated_docker_client = self ._docker_client_param or get_validated_container_client ()
202- return self ._validated_docker_client
208+ if self ._validated_container_client is None :
209+ self ._validated_container_client = self ._container_client_param or get_validated_container_client ()
210+ return self ._validated_container_client
203211
204212 def build (self ) -> ApplicationBuildResult :
205213 """
@@ -453,14 +461,28 @@ def _build_lambda_image(self, function_name: str, metadata: Dict, architecture:
453461 build_args ["target" ] = cast (str , docker_build_target )
454462
455463 try :
456- build_image , build_logs = self ._docker_client .images .build (** build_args )
457- LOG .debug ("%s image is built for %s function" , build_image , function_name )
464+ if not self ._image_build_client :
465+ if self ._use_buildkit :
466+ container_client = self ._container_client
467+ engine_type = container_client .get_runtime_type ()
468+
469+ is_available , error_msg = CLIBuildClient .is_available (engine_type )
470+ if not is_available :
471+ raise BuildkitNotAvailableException (error_msg )
472+
473+ self ._image_build_client = CLIBuildClient (engine_type = engine_type )
474+ LOG .debug (f"Using CLIBuildClient with engine_type { engine_type } " )
475+ else :
476+ self ._image_build_client = SDKBuildClient (self ._container_client )
477+ LOG .debug ("Using SDKBuildClient" )
478+ build_logs = self ._image_build_client .build_image (** build_args ) # type: ignore[arg-type]
479+ LOG .debug (f"Image built for { function_name } function" )
458480 except docker .errors .BuildError as ex :
459481 LOG .error ("Failed building function %s" , function_name )
460482 self ._stream_lambda_image_build_logs (ex .build_log , function_name , False )
461483 raise DockerBuildFailed (str (ex )) from ex
462484 except docker .errors .APIError as e :
463- if self ._docker_client .is_dockerfile_error (e ):
485+ if self ._container_client .is_dockerfile_error (e ):
464486 raise DockerfileOutSideOfContext (e .explanation ) from e
465487
466488 # Re-raise other API errors
@@ -469,9 +491,9 @@ def _build_lambda_image(self, function_name: str, metadata: Dict, architecture:
469491 # The Docker-py low level api will stream logs back but if an exception is raised by the api
470492 # this is raised when accessing the generator. So we need to wrap accessing build_logs in a try: except.
471493 try :
472- self ._stream_lambda_image_build_logs (build_logs , function_name )
494+ self ._stream_lambda_image_build_logs (build_logs , function_name ) # type: ignore[arg-type]
473495 except docker .errors .APIError as e :
474- if self ._docker_client .is_dockerfile_error (e ):
496+ if self ._container_client .is_dockerfile_error (e ):
475497 raise DockerfileOutSideOfContext (e .explanation ) from e
476498
477499 # Not sure what else can be raise that we should be catching but re-raising for now
@@ -501,7 +523,7 @@ def _stream_lambda_image_build_logs(
501523 def _load_lambda_image (self , image_archive_path : str ) -> str :
502524 try :
503525 with open (image_archive_path , mode = "rb" ) as image_archive :
504- image = self ._docker_client .load_image_from_archive (image_archive )
526+ image = self ._container_client .load_image_from_archive (image_archive )
505527 return f"{ image .id } "
506528 except (docker .errors .APIError , OSError , ContainerArchiveImageLoadFailedException ) as ex :
507529 raise DockerBuildFailed (msg = str (ex )) from ex
0 commit comments