Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e49d7d4
feat(build): Add sam build support for MicrovmImage
licjun Oct 21, 2026
8e42a40
fix(build): Resolve intrinsic CodeArtifact.Uri in the built template
licjun Oct 21, 2026
09e410c
feat(build): Report the MicrovmImage tag in the JSON build output
licjun Oct 21, 2026
1f48ed3
feat(build): Warn on cross-architecture MicrovmImage builds
licjun Oct 21, 2026
b458174
test(build): Exercise arm64 emulation in MicrovmImage integration tests
licjun Oct 21, 2026
771ecc4
test(build): Cover MicrovmImage inside Fn::ForEach with language exte…
licjun Oct 23, 2026
76d8ee5
fix(build): Point the built MicrovmImage at the directory it was buil…
licjun Oct 23, 2026
9933da7
fix(package): Fail sam package for local MicrovmImage code until supp…
licjun Oct 23, 2026
81f5c14
feat(build): List built MicrovmImages in the text build summary
licjun Oct 23, 2026
49c5b31
fix(build): Sanitize MicrovmImage tags and report other Docker API er…
licjun Oct 23, 2026
2c3f467
fix(build): Report specific MicrovmImage source errors
licjun Oct 23, 2026
93f98ac
feat(build): Warn when MicrovmImage Metadata Docker settings are ignored
licjun Oct 23, 2026
749383c
fix(build): Print one progress line per MicrovmImage build
licjun Oct 23, 2026
67b32bf
fix(build): Collect build resources once so skip warnings print once
licjun Oct 26, 2026
9ea5c65
fix(build): Only suggest --parameter-overrides for an unset Architect…
licjun Oct 26, 2026
05e9ed3
fix(build): Mention MicroVM images in build help and --cached output
licjun Oct 26, 2026
0fc2c67
fix(build): Skip MicrovmImages without a container runtime on build-all
licjun Oct 27, 2026
e28fcc0
feat(build): Warn when MicrovmImages map to the same local tag
licjun Oct 27, 2026
789e74a
refactor(build): Share the image build client setup
licjun Oct 27, 2026
d9fe65d
fix(build): Address MicrovmImage consistency review comments
licjun Oct 27, 2026
2f11ccd
Merge branch 'develop' into feat-microvm-image-build
licjun Oct 28, 2026
907f33c
Merge branch 'develop' into feat-microvm-image-build
licjun Oct 28, 2026
e695d6c
fix(build): Forward MicrovmImage builds in WorkspaceAwareBuildStrategy
licjun Oct 28, 2026
d83185d
style: Restore import grouping in app_builder.py
licjun Oct 28, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ jobs:
--ignore=tests/integration/buildcmd/test_build_in_source.py \
--ignore=tests/integration/buildcmd/test_build_samconfig.py \
--ignore=tests/integration/buildcmd/test_build_cmd_arm64.py \
--ignore=tests/integration/buildcmd/test_build_cmd_microvm_image.py \
--ignore=tests/integration/buildcmd/test_build_terraform_applications.py \
--ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py \
-k "not container" \
Expand All @@ -327,6 +328,7 @@ jobs:
"build-arm64")
pytest -vv -n 2 --reruns 3 -m "not requires_credential and not tier1_extra" \
tests/integration/buildcmd/test_build_cmd_arm64.py \
tests/integration/buildcmd/test_build_cmd_microvm_image.py \
-k "not container" \
--json-report --json-report-file=TEST_REPORT-${{ matrix.test_suite }}.json
;;
Expand Down
190 changes: 169 additions & 21 deletions samcli/commands/build/build_context.py

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions samcli/commands/build/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,21 @@
LOG = logging.getLogger(__name__)

HELP_TEXT = """
Build AWS serverless function code.
Build AWS serverless function code and MicroVM images.
"""

DESCRIPTION = """
Build AWS serverless function code to generate artifacts targeting
AWS Lambda execution environment.\n
AWS Lambda execution environment, and build MicroVM images locally.\n
\b
Supported Resource Types
------------------------
1. AWS::Serverless::Function\n
2. AWS::Lambda::Function\n
3. AWS::Serverless::LayerVersion\n
4. AWS::Lambda::LayerVersion\n
5. AWS::Serverless::MicrovmImage\n
6. AWS::Lambda::MicrovmImage\n
\b
Supported Runtimes
------------------
Expand Down Expand Up @@ -109,7 +111,10 @@
help="Name of the resource(s) to exclude from AWS SAM CLI build.",
)
@click.option(
"--parallel", "-p", is_flag=True, help="Enable parallel builds for AWS SAM template's functions and layers."
"--parallel",
"-p",
is_flag=True,
help="Enable parallel builds for AWS SAM template's functions, layers and MicroVM images.",
)
@click.option(
"--mount-with",
Expand Down
243 changes: 224 additions & 19 deletions samcli/lib/build/app_builder.py

Large diffs are not rendered by default.

74 changes: 73 additions & 1 deletion samcli/lib/build/build_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled
from samcli.lib.build.exceptions import InvalidBuildGraphException
from samcli.lib.providers.provider import BuildableFunction, LayerVersion, get_build_entry_point
from samcli.lib.providers.provider import BuildableFunction, LayerVersion, MicrovmImage, get_build_entry_point
from samcli.lib.samlib.resource_metadata_normalizer import (
SAM_IS_NORMALIZED,
SAM_RESOURCE_ID_KEY,
Expand Down Expand Up @@ -212,6 +212,7 @@ def __init__(self, build_dir: str) -> None:
self._filepath = Path(build_dir).parent.joinpath(DEFAULT_BUILD_GRAPH_FILE_NAME)
self._function_build_definitions: List["FunctionBuildDefinition"] = []
self._layer_build_definitions: List["LayerBuildDefinition"] = []
self._microvm_image_build_definitions: List["MicrovmImageBuildDefinition"] = []
self._atomic_read()

def get_function_build_definitions(self) -> Tuple["FunctionBuildDefinition", ...]:
Expand All @@ -220,6 +221,9 @@ def get_function_build_definitions(self) -> Tuple["FunctionBuildDefinition", ...
def get_layer_build_definitions(self) -> Tuple["LayerBuildDefinition", ...]:
return tuple(self._layer_build_definitions)

def get_microvm_image_build_definitions(self) -> Tuple["MicrovmImageBuildDefinition", ...]:
return tuple(self._microvm_image_build_definitions)

def get_function_build_definition_with_full_path(
self, function_full_path: str
) -> Optional["FunctionBuildDefinition"]:
Expand Down Expand Up @@ -315,11 +319,34 @@ def put_layer_build_definition(self, layer_build_definition: "LayerBuildDefiniti
layer_build_definition.layer = layer
self._layer_build_definitions.append(layer_build_definition)

def put_microvm_image_build_definition(
self, microvm_image_build_definition: "MicrovmImageBuildDefinition", microvm_image: MicrovmImage
) -> None:
"""
Puts the newly read MicroVM image build definition into existing build graph.

Unlike Function/Layer, MicrovmImage definitions are not persisted to build.toml (Docker has its own
layer cache), so there are no pre-existing definitions to match against. Each definition is always new.

Parameters
----------
microvm_image_build_definition: MicrovmImageBuildDefinition
MicroVM image build definition which is newly read from template.yaml file
microvm_image: MicrovmImage
MicroVM image details for this build definition
"""
microvm_image_build_definition.add_microvm_image(microvm_image)
self._microvm_image_build_definitions.append(microvm_image_build_definition)

def clean_redundant_definitions_and_update(self, persist: bool) -> None:
"""
Removes build definitions which doesn't have any function in it, which means these build definitions
are no longer used, and they can be deleted

Note: MicrovmImage build definitions are intentionally not cleaned up here. Unlike Function/Layer,
they are never read from build.toml, so every put_microvm_image_build_definition() call always
sets microvm_image before appending -- there is no code path that produces an empty definition.

If persist parameter is given True, build graph is written to .aws-sam/build.toml file
"""
self._function_build_definitions[:] = [
Expand Down Expand Up @@ -711,3 +738,48 @@ def __eq__(self, other: Any) -> bool:
and self.architecture == other.architecture
and self.resource_type == other.resource_type
)


class MicrovmImageBuildDefinition(AbstractBuildDefinition):
"""
MicrovmImageBuildDefinition holds information about each unique MicroVM image build.
Each MicrovmImage is built via docker build from its CodeUri directory.
"""

def __init__(
self,
full_path: str,
code_uri: str,
architecture: str,
source_hash: str = "",
manifest_hash: str = "",
env_vars: Optional[Dict] = None,
) -> None:
super().__init__(source_hash, manifest_hash, env_vars, architecture)
self.full_path = full_path
self.code_uri = code_uri
self.microvm_image: Optional["MicrovmImage"] = None

def add_microvm_image(self, microvm_image: "MicrovmImage") -> None:
self.microvm_image = microvm_image

def get_resource_full_paths(self) -> str:
"""Returns the MicroVM image's full path"""
if not self.microvm_image:
return ""
return self.microvm_image.full_path

def __eq__(self, other: Any) -> bool:
if not isinstance(other, MicrovmImageBuildDefinition):
return False
return (
self.full_path == other.full_path
and self.code_uri == other.code_uri
and self.architecture == other.architecture
)

def __hash__(self) -> int:
return hash((self.full_path, self.code_uri, self.architecture))

def __str__(self) -> str:
return f"MicrovmImageBuildDefinition({self.full_path}, {self.code_uri}, {self.architecture})"
94 changes: 86 additions & 8 deletions samcli/lib/build/build_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BuildGraph,
FunctionBuildDefinition,
LayerBuildDefinition,
MicrovmImageBuildDefinition,
)
from samcli.lib.build.dependency_hash_generator import DependencyHashGenerator
from samcli.lib.build.exceptions import (
Expand All @@ -37,9 +38,10 @@

LOG = logging.getLogger(__name__)

# type definition which can be used in generic types for both FunctionBuildDefinition & LayerBuildDefinition
FunctionOrLayerBuildDefinition = TypeVar(
"FunctionOrLayerBuildDefinition", FunctionBuildDefinition, LayerBuildDefinition
# type definition which can be used in generic types for FunctionBuildDefinition, LayerBuildDefinition,
# and MicrovmImageBuildDefinition
BuildDefinitionType = TypeVar(
"BuildDefinitionType", FunctionBuildDefinition, LayerBuildDefinition, MicrovmImageBuildDefinition
)

# Build failures that are attributable to specific resources. When one of these escapes a strategy
Expand Down Expand Up @@ -95,7 +97,7 @@ def clean_redundant_folders(base_dir: str, uuids: Set[str]) -> None:
class BuildStrategy(ABC):
"""
Base class for BuildStrategy
Keeps basic implementation of build, build_functions and build_layers
Keeps basic implementation of build, build_functions, build_layers, and build_microvm_images
"""

def __init__(self, build_graph: BuildGraph) -> None:
Expand All @@ -109,12 +111,13 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:

def build(self) -> Dict[str, str]:
"""
Builds all functions and layers in the given build graph
Builds all functions, layers, and MicrovmImages in the given build graph
"""
result = {}
with self:
result.update(self._build_layers(self._build_graph))
result.update(self._build_functions(self._build_graph))
result.update(self._build_microvm_images(self._build_graph))

return result

Expand Down Expand Up @@ -152,10 +155,28 @@ def build_single_layer_definition(self, layer_definition: LayerBuildDefinition)
build location as value
"""

def _build_microvm_images(self, build_graph: BuildGraph) -> Dict[str, str]:
"""
Iterates through build graph and runs each unique build for MicrovmImage resources
"""
microvm_build_results = {}
for microvm_definition in build_graph.get_microvm_image_build_definitions():
microvm_build_results.update(self.build_single_microvm_image_definition(microvm_definition))

return microvm_build_results

@abstractmethod
def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
Comment thread
licjun marked this conversation as resolved.
"""
Builds single MicrovmImage definition and returns dictionary which contains MicrovmImage full path as key,
Docker image tag as value
"""


class DefaultBuildStrategy(BuildStrategy):
"""
Default build strategy, loops over given build graph for each function and layer, and builds each of them one by one
Default build strategy, loops over given build graph for each function, layer, and MicrovmImage,
and builds each of them one by one
"""

def __init__(
Expand All @@ -166,12 +187,14 @@ def __init__(
[str, str, Optional[str], str, str, str, Optional[str], str, dict, dict, Optional[str], bool], str
],
build_layer: Callable[[str, str, str, List[str], str, str, dict, Optional[str], bool, Optional[Dict]], str],
build_microvm_image: Callable[[str, str, str], str],
cached: bool = False,
) -> None:
super().__init__(build_graph)
self._build_dir = build_dir
self._build_function = build_function
self._build_layer = build_layer
self._build_microvm_image = build_microvm_image
self._cached = cached

def build_single_function_definition(self, build_definition: FunctionBuildDefinition) -> Dict[str, str]:
Expand Down Expand Up @@ -302,6 +325,29 @@ def _do_build_single_layer_definition(self, layer_definition: LayerBuildDefiniti
)
}

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
"""
Build a single MicrovmImage definition via docker build.
"""
microvm_image = microvm_definition.microvm_image
if not microvm_image or not microvm_image.architecture:
LOG.warning(
"Skipping MicrovmImage build: missing image or architecture for definition %s",
microvm_definition.code_uri,
)
return {}
with _attribute_build_failure_to([microvm_image.full_path]):
LOG.info(
"Building MicrovmImage: %s from %s",
microvm_image.full_path,
microvm_definition.code_uri,
)

result = self._build_microvm_image(
microvm_image.full_path, microvm_definition.code_uri, microvm_image.architecture
)
return {microvm_image.full_path: result}


class CachedBuildStrategy(BuildStrategy):
"""
Expand Down Expand Up @@ -444,6 +490,18 @@ def _clean_redundant_cached(self) -> None:
uuids.update({ld.uuid for ld in self._build_graph.get_layer_build_definitions()})
clean_redundant_folders(self._cache_dir, uuids)

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
"""
Builds single MicrovmImage definition. Delegates to the underlying build strategy since
Docker has its own layer caching.
"""
if microvm_definition.microvm_image:
LOG.info(
"MicrovmImage %s is not cached by SAM CLI; Docker's layer cache is used",
microvm_definition.microvm_image.full_path,
)
return self._delegate_build_strategy.build_single_microvm_image_definition(microvm_definition)


class ParallelBuildStrategy(BuildStrategy):
"""
Expand Down Expand Up @@ -472,10 +530,15 @@ def _build_functions(self, build_graph: BuildGraph) -> Dict[str, str]:
self.build_single_function_definition, build_graph.get_function_build_definitions()
)

def _build_microvm_images(self, build_graph: BuildGraph) -> Dict[str, str]:
return self._run_builds_async(
self.build_single_microvm_image_definition, build_graph.get_microvm_image_build_definitions()
)

@staticmethod
def _run_builds_async(
build_method: Callable[[FunctionOrLayerBuildDefinition], Dict[str, str]],
build_definitions: Tuple[FunctionOrLayerBuildDefinition, ...],
build_method: Callable[[BuildDefinitionType], Dict[str, str]],
build_definitions: Tuple[BuildDefinitionType, ...],
) -> Dict[str, str]:
"""Builds given list of build definitions in async and return the result"""
if not build_definitions:
Expand All @@ -497,6 +560,9 @@ def build_single_layer_definition(self, layer_definition: LayerBuildDefinition)
def build_single_function_definition(self, build_definition: FunctionBuildDefinition) -> Dict[str, str]:
return self._delegate_build_strategy.build_single_function_definition(build_definition)

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
return self._delegate_build_strategy.build_single_microvm_image_definition(microvm_definition)


class IncrementalBuildStrategy(BuildStrategy):
"""
Expand Down Expand Up @@ -542,6 +608,9 @@ def build_single_layer_definition(self, layer_definition: LayerBuildDefinition)
)
return self._delegate_build_strategy.build_single_layer_definition(layer_definition)

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
return self._delegate_build_strategy.build_single_microvm_image_definition(microvm_definition)

def _check_whether_manifest_is_changed(
self,
build_definition: AbstractBuildDefinition,
Expand Down Expand Up @@ -665,6 +734,9 @@ def build_single_layer_definition(self, layer_definition: LayerBuildDefinition)
)
return self._cached_build_strategy.build_single_layer_definition(layer_definition)

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
return self._cached_build_strategy.build_single_microvm_image_definition(microvm_definition)

def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
"""
After build is complete, this method cleans up redundant folders in cached directory as well as in dependencies
Expand Down Expand Up @@ -762,6 +834,12 @@ def build_single_function_definition(self, build_definition: FunctionBuildDefini
def build_single_layer_definition(self, layer_definition: LayerBuildDefinition) -> Dict[str, str]:
return self._delegate_build_strategy.build_single_layer_definition(layer_definition)

def _build_microvm_images(self, build_graph: BuildGraph) -> Dict[str, str]:
return self._delegate_build_strategy._build_microvm_images(build_graph)

def build_single_microvm_image_definition(self, microvm_definition: MicrovmImageBuildDefinition) -> Dict[str, str]:
return self._delegate_build_strategy.build_single_microvm_image_definition(microvm_definition)

def _prepare_shared_project_for_workspace_groups(self, build_graph: BuildGraph) -> None:
groups: Dict[str, List[FunctionBuildDefinition]] = {}
for build_definition in build_graph.get_function_build_definitions():
Expand Down
7 changes: 7 additions & 0 deletions samcli/lib/build/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def __init__(self, msg: str) -> None:
BuildError.__init__(self, "DockerBuildFailed", msg)


class MicrovmImageSourceError(BuildError):
"""A MicrovmImage's source can't be built locally; raised before any Docker build runs."""

def __init__(self, msg: str) -> None:
BuildError.__init__(self, "MicrovmImageSourceError", msg)


class MissingBuildMethodException(BuildError):
def __init__(self, msg: str) -> None:
BuildError.__init__(self, "MissingBuildMethodException", msg)
Expand Down
Loading
Loading