Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions samcli/commands/_utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,20 @@ def use_container_build_option(f):
return use_container_build_click_option()(f)


def use_buildkit_click_option():
return click.option(
"--use-buildkit/--no-use-buildkit",
required=False,
default=False,
is_flag=True,
help="Enable buildkit for container image builds. Requires Docker with buildx plugin or Finch CLI.",
)


def use_buildkit_option(f):
return use_buildkit_click_option()(f)


def mount_symlinks_click_option():
return click.option(
"--mount-symlinks/--no-mount-symlinks",
Expand Down
24 changes: 24 additions & 0 deletions samcli/commands/build/build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
from samcli.lib.providers.sam_stack_provider import SamLocalStackProvider
from samcli.lib.telemetry.event import EventName, EventTracker, UsedFeature
from samcli.lib.utils.osutils import BUILD_DIR_PERMISSIONS
from samcli.local.docker.build_client import BuildClient, CLIBuildClient
from samcli.local.docker.container_client_factory import ContainerClientFactory
from samcli.local.docker.exceptions import BuildkitNotAvailableException
from samcli.local.docker.manager import ContainerManager
from samcli.local.lambdafn.exceptions import (
FunctionNotFound,
Expand Down Expand Up @@ -83,6 +86,7 @@ def __init__(
build_in_source: Optional[bool] = None,
mount_with: str = MountMode.READ.value,
mount_symlinks: Optional[bool] = False,
use_buildkit: Optional[bool] = False,
) -> None:
"""
Initialize the class
Expand Down Expand Up @@ -142,6 +146,8 @@ def __init__(
Mount mode of source code directory when building inside container, READ ONLY by default
mount_symlinks Optional[bool]:
Indicates if symlinks should be mounted inside the container
use_buildkit Optional[bool]:
Enable buildkit for container image builds
"""

self._resource_identifier = resource_identifier
Expand Down Expand Up @@ -177,13 +183,15 @@ def __init__(
self._function_provider: Optional[SamFunctionProvider] = None
self._layer_provider: Optional[SamLayerProvider] = None
self._container_manager: Optional[ContainerManager] = None
self._build_client: Optional[BuildClient] = None
self._stacks: List[Stack] = []
self._locate_layer_nested = locate_layer_nested
self._hook_name = hook_name
self._build_in_source = build_in_source
self._build_result: Optional[ApplicationBuildResult] = None
self._mount_with = MountMode(mount_with)
self._mount_symlinks = mount_symlinks
self._use_buildkit = use_buildkit

def __enter__(self) -> "BuildContext":
self.set_up()
Expand Down Expand Up @@ -231,6 +239,17 @@ def set_up(self) -> None:
docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image
)

if self._use_buildkit:
container_client = ContainerClientFactory.create_client()
engine_type = container_client.get_runtime_type()

is_available, error_msg = CLIBuildClient.is_available(engine_type)
if not is_available:
raise BuildkitNotAvailableException(error_msg)

self._build_client = CLIBuildClient(engine_type=engine_type)
LOG.info(f"Using buildkit with {engine_type}")

def __exit__(self, *args):
pass

Expand Down Expand Up @@ -278,6 +297,7 @@ def run(self) -> None:
build_in_source=self._build_in_source,
mount_with_write=mount_with_write,
mount_symlinks=self._mount_symlinks,
build_client=self.build_client,
)

self._check_exclude_warning()
Expand Down Expand Up @@ -480,6 +500,10 @@ def _setup_build_dir(build_dir: str, clean: bool) -> str:
def container_manager(self) -> Optional[ContainerManager]:
return self._container_manager

@property
def build_client(self):
return self._build_client

@property
def function_provider(self) -> SamFunctionProvider:
# Note(xinhol): despite self._function_provider is Optional
Expand Down
6 changes: 6 additions & 0 deletions samcli/commands/build/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
skip_prepare_infra_option,
template_option_without_build,
terraform_project_root_path_option,
use_buildkit_option,
use_container_build_option,
)
from samcli.commands.build.click_container import ContainerOptions
Expand Down Expand Up @@ -82,6 +83,7 @@
)
@skip_prepare_infra_option
@use_container_build_option
@use_buildkit_option
@build_in_source_option
@click.option(
"--container-env-var",
Expand Down Expand Up @@ -161,6 +163,7 @@ def cli(
terraform_project_root_path: Optional[str],
build_in_source: Optional[bool],
mount_symlinks: Optional[bool],
use_buildkit: Optional[bool],
) -> None:
"""
`sam build` command entry point
Expand Down Expand Up @@ -193,6 +196,7 @@ def cli(
build_in_source,
mount_with,
mount_symlinks,
use_buildkit,
) # pragma: no cover


Expand Down Expand Up @@ -220,6 +224,7 @@ def do_cli( # pylint: disable=too-many-locals, too-many-statements
build_in_source: Optional[bool],
mount_with: str,
mount_symlinks: Optional[bool],
use_buildkit: Optional[bool],
) -> None:
"""
Implementation of the ``cli`` method
Expand Down Expand Up @@ -260,6 +265,7 @@ def do_cli( # pylint: disable=too-many-locals, too-many-statements
build_in_source=build_in_source,
mount_with=mount_with,
mount_symlinks=mount_symlinks,
use_buildkit=use_buildkit,
) as ctx:
ctx.run()

Expand Down
1 change: 1 addition & 0 deletions samcli/commands/build/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

CONTAINER_OPTION_NAMES: List[str] = [
"use_container",
"use_buildkit",
"container_env_var",
"container_env_var_file",
"build_image",
Expand Down
15 changes: 12 additions & 3 deletions samcli/lib/build/app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
AWS_SERVERLESS_LAYERVERSION,
)
from samcli.lib.utils.stream_writer import StreamWriter
from samcli.local.docker.build_client import BuildClient
from samcli.local.docker.container import ContainerContext
from samcli.local.docker.exceptions import ContainerArchiveImageLoadFailedException
from samcli.local.docker.lambda_build_container import LambdaBuildContainer
Expand Down Expand Up @@ -114,6 +115,7 @@ def __init__(
build_in_source: Optional[bool] = None,
mount_with_write: bool = False,
mount_symlinks: Optional[bool] = False,
build_client: Optional[BuildClient] = None,
) -> None:
"""
Initialize the class
Expand Down Expand Up @@ -161,6 +163,8 @@ def __init__(
Mount source code directory with write permissions when building inside container.
mount_symlinks: Optional[bool]
True if symlinks should be mounted in the container.
build_client: Optional[BuildClient]
Optional build client for building Image functions with buildkit support.
Comment thread
reedham-aws marked this conversation as resolved.
Outdated
"""
self._resources_to_build = resources_to_build
self._build_dir = build_dir
Expand Down Expand Up @@ -190,6 +194,7 @@ def __init__(
self._build_in_source = build_in_source
self._mount_with_write = mount_with_write
self._mount_symlinks = mount_symlinks
self._build_client = build_client

@property
def _docker_client(self) -> docker.DockerClient:
Expand Down Expand Up @@ -453,8 +458,12 @@ def _build_lambda_image(self, function_name: str, metadata: Dict, architecture:
build_args["target"] = cast(str, docker_build_target)

try:
(build_image, build_logs) = self._docker_client.images.build(**build_args)
LOG.debug("%s image is built for %s function", build_image, function_name)
if self._build_client:
build_logs = self._build_client.build_image(**build_args) # type: ignore[arg-type]
LOG.debug(f"Image build for {function_name} function using build client")
else:
(build_image, build_logs) = self._docker_client.images.build(**build_args)
Comment thread
reedham-aws marked this conversation as resolved.
Outdated
LOG.debug("%s image is built for %s function", build_image, function_name)
except docker.errors.BuildError as ex:
LOG.error("Failed building function %s", function_name)
self._stream_lambda_image_build_logs(ex.build_log, function_name, False)
Expand All @@ -469,7 +478,7 @@ def _build_lambda_image(self, function_name: str, metadata: Dict, architecture:
# The Docker-py low level api will stream logs back but if an exception is raised by the api
# this is raised when accessing the generator. So we need to wrap accessing build_logs in a try: except.
try:
self._stream_lambda_image_build_logs(build_logs, function_name)
self._stream_lambda_image_build_logs(build_logs, function_name) # type: ignore[arg-type]
except docker.errors.APIError as e:
if self._docker_client.is_dockerfile_error(e):
raise DockerfileOutSideOfContext(e.explanation) from e
Expand Down
Loading
Loading