-
Notifications
You must be signed in to change notification settings - Fork 147
feat: Rust cargo lambda workflow #350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
44a524f
4099290
1d4377b
ee7ca39
cc91826
3a97629
3be9ba2
5d0e9d2
4adb733
ccb7923
4a1d43f
c2ea1c4
7fdab80
692f45b
1f9864d
7566af6
f3cb498
f100e78
2966fb6
28a1a6b
2c28d2a
8618cfe
9c968d1
3f60f46
e513d57
bab95cb
cc7447c
3c518d5
1f63ada
5848c00
d6c1e85
52ae803
fbe1b93
158195d
9630fad
22a84c8
4d2dd05
b02dedd
12acb4c
a91f56e
0dfde75
2c58e29
ecca29b
f48f1bf
abe6c3a
05a6cd9
f363a9c
88c6728
61342f9
bcb6ff8
b624579
ab39fa5
52caab0
b408412
8f540c9
97563d3
44b3742
b442bd7
943c9c7
b262ccd
9df26cb
27513a5
7b8aecd
e327c27
0266ddb
07a2487
c553d5e
da5de28
2d6ab07
3dbf9d7
39da946
5e642a8
e119447
3539bbb
07edae9
42f315b
65efcd8
9b87138
c1ae732
111ea79
2b5d728
53a36a7
def9544
7d697ca
036bd44
bcbe142
c947f6c
64cc813
e015be0
2237d76
e591484
0accf64
a5c720a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Rust Cargo Builder | ||
|
||
## Scope | ||
|
||
This package enables the creation of a Lambda deployment package for Rust projects managed using the [cargo](https://doc.rust-lang.org/cargo/) build tool targeting Lambda's "provided" runtime. Rust support for the provided runtime is bundled as a compilation dependency of these projects, provided by the [lambda](https://github.com/awslabs/aws-lambda-rust-runtime) crate. | ||
|
||
## Implementation | ||
|
||
This package uses [Cargo Lambda](https://www.cargo-lambda.info) to do all the heavy lifting for cross compilation, target validation, and other executable optimizations. | ||
|
||
It supports X86-64 architectures with the target `x86_64-unknown-linux-gnu` by default. It also supports ARM architectures with the target option `aarch64-unknown-linux-gnu`. Those are the only two valid targets. The target is automatically configured based on the `architecture` option in the `RustCargoLambdaWorkflow`. | ||
|
||
The general algorithm for preparing a rust executable for use on AWS Lambda is as follows. | ||
|
||
### Build | ||
|
||
It builds a binary in the standard cargo target directory. The binary's name is always `bootstrap`, and it's always located under `target/lambda/HANDLER_NAME/bootstrap`. | ||
|
||
### Copy and Rename executable | ||
|
||
It then copies the executable to the target directory honoring the provided runtime's [expectation on executable names](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html). | ||
|
||
## Notes | ||
|
||
Like the go builders, the workflow argument `options.artifact_executable_name` | ||
interface can used to provide a handler name that resolves to an executable. This | ||
enables sam support for cargo workspaces allowing for one rust project to have multiple lambdas. Cargo workspaces have a notion of a `package` and `bin`. A `package` can have | ||
multiple bins but typically `packages` have a 1-to-1 relationship with a default `bin`: `main.rs`. The handler names must be uniques across a Rust project, regardless of how many packages and binaries that project includes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
""" | ||
Builds Rust Lambda functions using Cargo Lambda | ||
""" | ||
|
||
from .workflow import RustCargoLambdaWorkflow |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
""" | ||
Rust Cargo build actions | ||
""" | ||
|
||
import logging | ||
import os | ||
|
||
from aws_lambda_builders.workflow import BuildMode | ||
from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose | ||
from aws_lambda_builders.architecture import X86_64, ARM64 | ||
from .exceptions import CargoLambdaExecutionException | ||
from .utils import OSUtils | ||
|
||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
class RustCargoLambdaBuildAction(BaseAction): | ||
NAME = "CargoLambdaBuild" | ||
DESCRIPTION = "Building the project using Cargo Lambda" | ||
PURPOSE = Purpose.COMPILE_SOURCE | ||
|
||
def __init__( | ||
self, | ||
source_dir, | ||
binaries, | ||
mode, | ||
subprocess_cargo_lambda, | ||
architecture=X86_64, | ||
handler=None, | ||
flags=None, | ||
): | ||
""" | ||
Build the a Rust executable | ||
|
||
:type source_dir: str | ||
calavera marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:param source_dir: | ||
Path to a folder containing the source code | ||
|
||
:type binaries: dict | ||
:param binaries: | ||
Resolved path dependencies | ||
|
||
:type mode: str | ||
:param mode: | ||
Mode the build should produce | ||
|
||
:type architecture: str, optional | ||
:param architecture: | ||
Target architecture to build the binary, either arm64 or x86_64 | ||
|
||
:type handler: str, optional | ||
:param handler: | ||
Handler name in `bin_name` format | ||
|
||
:type flags: list, optional | ||
:param flags: | ||
Extra list of flags to pass to `cargo lambda build` | ||
|
||
:type subprocess_cargo_lambda: aws_lambda_builders.workflows.rust_cargo.cargo_lambda.SubprocessCargoLambda | ||
:param subprocess_cargo_lambda: An instance of the Cargo Lambda process wrapper | ||
""" | ||
|
||
self._source_dir = source_dir | ||
self._mode = mode | ||
self._binaries = binaries | ||
self._handler = handler | ||
self._flags = flags | ||
self._architecture = architecture | ||
self._subprocess_cargo_lambda = subprocess_cargo_lambda | ||
|
||
def build_command(self): | ||
cmd = [self._binaries["cargo"].binary_path, "lambda", "build"] | ||
if self._mode != BuildMode.DEBUG: | ||
cmd.append("--release") | ||
hawflau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self._architecture == ARM64: | ||
cmd.append("--arm64") | ||
if self._handler: | ||
cmd.extend(["--bin", self._handler]) | ||
if self._flags: | ||
cmd.extend(self._flags) | ||
|
||
return cmd | ||
|
||
def execute(self): | ||
try: | ||
return self._subprocess_cargo_lambda.run(command=self.build_command(), cwd=self._source_dir) | ||
except CargoLambdaExecutionException as ex: | ||
raise ActionFailedError(str(ex)) | ||
|
||
|
||
class RustCopyAndRenameAction(BaseAction): | ||
NAME = "RustCopyAndRename" | ||
DESCRIPTION = "Copy Rust executable, renaming if needed" | ||
PURPOSE = Purpose.COPY_SOURCE | ||
|
||
def __init__(self, source_dir, artifacts_dir, handler=None, osutils=OSUtils()): | ||
""" | ||
Copy and rename Rust executable | ||
|
||
Parameters | ||
---------- | ||
source_dir : str | ||
Path to a folder containing the source code | ||
|
||
artifacts_dir : str | ||
Path to a folder containing the deployable artifacts | ||
|
||
handler : str, optional | ||
Handler name in `package.bin_name` or `bin_name` format | ||
|
||
osutils : aws_lambda_builders.workflows.rust_cargo.utils.OSUtils, optional | ||
Optional, External IO utils | ||
""" | ||
self._source_dir = source_dir | ||
self._handler = handler | ||
self._artifacts_dir = artifacts_dir | ||
self._osutils = osutils | ||
|
||
def base_path(self): | ||
return os.path.join(self._source_dir, "target", "lambda") | ||
|
||
def binary_path(self): | ||
base = self.base_path() | ||
if self._handler: | ||
binary_path = os.path.join(base, self._handler, "bootstrap") | ||
LOG.debug("copying function binary from %s", binary_path) | ||
return binary_path | ||
|
||
output = os.listdir(base) | ||
if len(output) == 1: | ||
binary_path = os.path.join(base, output[0], "bootstrap") | ||
LOG.debug("copying function binary from %s", binary_path) | ||
return binary_path | ||
|
||
LOG.warning("unexpected list of binary directories: [%s]", ", ".join(output)) | ||
raise CargoLambdaExecutionException( | ||
message="unable to find function binary, use the option `artifact_executable_name` to specify the name" | ||
) | ||
|
||
def execute(self): | ||
self._osutils.makedirs(self._artifacts_dir) | ||
binary_path = self.binary_path() | ||
destination_path = os.path.join(self._artifacts_dir, "bootstrap") | ||
LOG.debug("copying function binary from %s to %s", binary_path, destination_path) | ||
self._osutils.copyfile(binary_path, destination_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did some integration tests and found that this doesn't copy the permission bits of the executable. We should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for catching that @hawflau ! I've updated the code to use |
Uh oh!
There was an error while loading. Please reload this page.