Skip to content

Commit 592237d

Browse files
authored
Add multi-architecture canary support (ARM64 Lambda) (#4606)
## Motivation Addresses #4380. Supersedes #4428. The smithy-rs canary only deploys x86_64 Lambda functions. This meant the [crc-fast 1.4 SIGILL incident](#4264) was not caught by smithy-rs CI it was discovered later in the aws-sdk-rust canary, after release. This PR expands the canary to also deploy **ARM64 (aarch64) Lambda functions** on AWS Graviton, catching architecture-specific runtime bugs before merge. ## Approach The existing x86_64 canary runs inside a Docker container via the `docker-build` action. The Docker image is x86_64-only in ECR, so instead of fixing Docker for ARM, this PR adds a **separate `canary-arm` job** that runs directly on `ubuntu-24.04-arm` without Docker. The ARM runner compiles the canary natively, deploys an ARM64 Lambda on Graviton, and exercises real S3/checksum code paths — the same paths where crc-fast SIGILL would manifest. ## Changes **Canary runner (`run.rs`):** - Add `--architecture` CLI flag to `RunArgs`/`Options` - Wire architecture through to `build_bundle()` (replaces hardcoded x86_64) - Set `.architectures(lambda_arch)` on Lambda creation (ARM64 → `Arm64`) - Use `provided.al2023` runtime for ARM64 (al2 is deprecated) - Architecture-suffixed Lambda function names to prevent parallel collisions **Build bundle (`build_bundle.rs`):** - Skip `cross` tool when building natively on ARM (`std::env::consts::ARCH` check) - Reduce hash truncation from 24→16 chars to leave headroom for arch suffix in 64-char Lambda name limit **Run script (`run-canary`):** - Accept optional 5th argument for architecture (defaults to `x86_64`) - Pass `--architecture` to canary runner **CI workflows (`ci.yml`, `manual-canary.yml`):** - Add new `canary-arm` job on `ubuntu-24.04-arm` (no Docker) - Existing x86_64 canary is completely unchanged ## Testing - Canary runner: `cargo check` ✓, `cargo clippy` ✓, `cargo test` 11/11 ✓ - YAML validation: both workflow files pass syntax check - Full CI verification pending (this PR) ## What is NOT changed - Existing x86_64 canary behavior (zero risk) - Docker infrastructure (`Dockerfile`, `acquire-build-image`, `docker-build` action) - Any runtime crates, codegen, or Smithy models - `arch.rs` (already supports `Aarch64`)
1 parent d6f5680 commit 592237d

7 files changed

Lines changed: 156 additions & 26 deletions

File tree

.github/workflows/ci.yml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,55 @@ jobs:
371371
uses: ./smithy-rs/.github/actions/docker-build
372372
with:
373373
action: run-canary
374-
action-arguments: ${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} ${{ steps.creds.outputs.aws-access-key-id }} ${{ steps.creds.outputs.aws-secret-access-key }} ${{ steps.creds.outputs.aws-session-token }}
374+
action-arguments: ${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} ${{ steps.creds.outputs.aws-access-key-id }} ${{ steps.creds.outputs.aws-secret-access-key }} ${{ steps.creds.outputs.aws-session-token }} x86_64
375+
376+
canary-arm:
377+
name: Canary (aarch64)
378+
if: ${{ inputs.run_canary }}
379+
needs: generate
380+
runs-on: ubuntu-24.04-arm
381+
timeout-minutes: 30
382+
permissions:
383+
id-token: write
384+
contents: read
385+
steps:
386+
- uses: actions/checkout@v4
387+
with:
388+
path: smithy-rs
389+
ref: ${{ inputs.git_ref }}
390+
- name: Download artifacts
391+
uses: actions/download-artifact@v4
392+
with:
393+
name: artifacts-generate-aws-sdk
394+
path: artifacts-generate-aws-sdk
395+
- name: Extract artifacts
396+
run: tar xfz artifacts-generate-aws-sdk/artifacts-generate-aws-sdk.tar.gz
397+
- name: Install Rust
398+
uses: dtolnay/rust-toolchain@stable
399+
with:
400+
targets: aarch64-unknown-linux-musl
401+
- name: Install build dependencies
402+
run: sudo apt-get update && sudo apt-get install -y musl-tools cmake perl clang pkg-config
403+
- name: Configure credentials
404+
id: creds
405+
uses: aws-actions/configure-aws-credentials@v4
406+
with:
407+
role-to-assume: ${{ secrets.CANARY_GITHUB_ACTIONS_ROLE_ARN }}
408+
role-session-name: GitHubActions
409+
aws-region: us-west-2
410+
output-credentials: true
411+
- name: Run canary
412+
run: |
413+
export RUST_STABLE_VERSION="$(rustc --version | cut -d' ' -f2)"
414+
# Install the pinned toolchain from rust-toolchain.toml and add musl target
415+
rustup toolchain install 1.91.1
416+
rustup target add aarch64-unknown-linux-musl wasm32-wasip2 --toolchain 1.91.1
417+
./smithy-rs/tools/ci-scripts/run-canary \
418+
${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} \
419+
${{ steps.creds.outputs.aws-access-key-id }} \
420+
${{ steps.creds.outputs.aws-secret-access-key }} \
421+
${{ steps.creds.outputs.aws-session-token }} \
422+
aarch64
375423
376424
# This is always a failing job since forked repositories do not have necessary repository secrets
377425
# to run the PR bot workflow or the canary workflow

.github/workflows/manual-canary.yml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,53 @@ jobs:
117117
uses: ./smithy-rs/.github/actions/docker-build
118118
with:
119119
action: run-canary
120-
action-arguments: ${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} ${{ steps.creds.outputs.aws-access-key-id }} ${{ steps.creds.outputs.aws-secret-access-key }} ${{ steps.creds.outputs.aws-session-token }}
120+
action-arguments: ${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} ${{ steps.creds.outputs.aws-access-key-id }} ${{ steps.creds.outputs.aws-secret-access-key }} ${{ steps.creds.outputs.aws-session-token }} x86_64
121+
122+
canary-arm:
123+
name: Canary (aarch64)
124+
needs:
125+
- generate
126+
- get-pr-info
127+
runs-on: ubuntu-24.04-arm
128+
timeout-minutes: 30
129+
permissions:
130+
id-token: write
131+
contents: read
132+
steps:
133+
- uses: actions/checkout@v4
134+
with:
135+
path: smithy-rs
136+
ref: ${{ inputs.commit_sha }}
137+
- name: Download artifacts
138+
uses: actions/download-artifact@v4
139+
with:
140+
name: artifacts-generate-aws-sdk-for-canary
141+
path: artifacts-generate-aws-sdk-for-canary
142+
- name: Extract artifacts
143+
run: tar xfz artifacts-generate-aws-sdk-for-canary/artifacts-generate-aws-sdk-for-canary.tar.gz
144+
- name: Install Rust
145+
uses: dtolnay/rust-toolchain@stable
146+
with:
147+
targets: aarch64-unknown-linux-musl
148+
- name: Install build dependencies
149+
run: sudo apt-get update && sudo apt-get install -y musl-tools cmake perl clang pkg-config
150+
- name: Configure credentials
151+
id: creds
152+
uses: aws-actions/configure-aws-credentials@v4
153+
with:
154+
role-to-assume: ${{ secrets.CANARY_GITHUB_ACTIONS_ROLE_ARN }}
155+
role-session-name: GitHubActions
156+
aws-region: us-west-2
157+
output-credentials: true
158+
- name: Run canary
159+
run: |
160+
export RUST_STABLE_VERSION="$(rustc --version | cut -d' ' -f2)"
161+
# Install the pinned toolchain from rust-toolchain.toml and add musl target
162+
rustup toolchain install 1.91.1
163+
rustup target add aarch64-unknown-linux-musl wasm32-wasip2 --toolchain 1.91.1
164+
./smithy-rs/tools/ci-scripts/run-canary \
165+
${{ secrets.CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME }} \
166+
${{ steps.creds.outputs.aws-access-key-id }} \
167+
${{ steps.creds.outputs.aws-secret-access-key }} \
168+
${{ steps.creds.outputs.aws-session-token }} \
169+
aarch64

tools/ci-cdk/canary-runner/src/arch.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,21 @@ impl std::str::FromStr for Arch {
2222
}
2323
}
2424
}
25+
26+
impl From<Arch> for aws_sdk_lambda::types::Architecture {
27+
fn from(arch: Arch) -> Self {
28+
match arch {
29+
Arch::X86_64 => aws_sdk_lambda::types::Architecture::X8664,
30+
Arch::Aarch64 => aws_sdk_lambda::types::Architecture::Arm64,
31+
}
32+
}
33+
}
34+
35+
impl From<Arch> for aws_sdk_lambda::types::Runtime {
36+
fn from(arch: Arch) -> Self {
37+
match arch {
38+
Arch::X86_64 => aws_sdk_lambda::types::Runtime::Providedal2,
39+
Arch::Aarch64 => aws_sdk_lambda::types::Runtime::Providedal2023,
40+
}
41+
}
42+
}

tools/ci-cdk/canary-runner/src/bench.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,6 @@ async fn create_lambda_fn(
257257
Ok(())
258258
}
259259

260-
impl From<Arch> for lambda::types::Architecture {
261-
fn from(arch: Arch) -> Self {
262-
match arch {
263-
Arch::X86_64 => lambda::types::Architecture::X8664,
264-
Arch::Aarch64 => lambda::types::Architecture::Arm64,
265-
}
266-
}
267-
}
268-
269260
async fn run_benchmark(
270261
lambda_client: &lambda::Client,
271262
bundle_name: &str,

tools/ci-cdk/canary-runner/src/build_bundle.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,9 @@ fn name_hashed_bundle(
333333
rust_version: Option<&str>,
334334
sdk_release_tag: Option<&ReleaseTag>,
335335
) -> Result<String> {
336-
// The Lambda name must be less than 64 characters, so truncate the hash a bit
337-
let bin_hash = &bin_hash[..24];
336+
// The Lambda name must be less than 64 characters, so truncate the hash a bit.
337+
// Using 16 chars leaves headroom for the architecture suffix added in run.rs.
338+
let bin_hash = &bin_hash[..16];
338339
// Lambda function names can't have periods in them
339340
let rust_version = rust_version.map(|s| s.replace('.', ""));
340341
let rust_version = rust_version.as_deref().unwrap_or("unknown");
@@ -390,8 +391,8 @@ pub async fn build_bundle(opt: BuildBundleArgs) -> Result<Option<PathBuf>> {
390391
};
391392

392393
if !opt.manifest_only {
393-
// Check if cross is needed and available
394-
let use_cross = opt.architecture == Arch::Aarch64;
394+
// Only use cross when cross-compiling (host arch != target arch)
395+
let use_cross = opt.architecture == Arch::Aarch64 && std::env::consts::ARCH != "aarch64";
395396
if use_cross {
396397
let cross_check = Command::new("cross").arg("--version").output();
397398
if cross_check.is_err() || !cross_check.unwrap().status.success() {
@@ -883,7 +884,7 @@ aws-smithy-wasm = { version = "0.1.0" }
883884
assert_eq!(expected, actual);
884885
}
885886
check(
886-
"canary-release20221216-1621-7ae6085d2105d5d1e13b10f8.zip",
887+
"canary-release20221216-1621-7ae6085d2105d5d1.zip",
887888
&name_hashed_bundle(
888889
"7ae6085d2105d5d1e13b10f882c6cb072ff5bbf8",
889890
Some("1.62.1"),
@@ -892,7 +893,7 @@ aws-smithy-wasm = { version = "0.1.0" }
892893
.unwrap(),
893894
);
894895
check(
895-
"canary-release202212162-1621-7ae6085d2105d5d1e13b10f8.zip",
896+
"canary-release202212162-1621-7ae6085d2105d5d1.zip",
896897
&name_hashed_bundle(
897898
"7ae6085d2105d5d1e13b10f882c6cb072ff5bbf8",
898899
Some("1.62.1"),
@@ -901,7 +902,7 @@ aws-smithy-wasm = { version = "0.1.0" }
901902
.unwrap(),
902903
);
903904
check(
904-
"canary-untagged-1621-7ae6085d2105d5d1e13b10f8.zip",
905+
"canary-untagged-1621-7ae6085d2105d5d1.zip",
905906
&name_hashed_bundle(
906907
"7ae6085d2105d5d1e13b10f882c6cb072ff5bbf8",
907908
Some("1.62.1"),
@@ -910,7 +911,7 @@ aws-smithy-wasm = { version = "0.1.0" }
910911
.unwrap(),
911912
);
912913
check(
913-
"canary-release20221216-unknown-7ae6085d2105d5d1e13b10f8.zip",
914+
"canary-release20221216-unknown-7ae6085d2105d5d1.zip",
914915
&name_hashed_bundle(
915916
"7ae6085d2105d5d1e13b10f882c6cb072ff5bbf8",
916917
None,

tools/ci-cdk/canary-runner/src/run.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ pub struct RunArgs {
119119
/// The ARN of the role that the Lambda will execute as
120120
#[clap(long, required_unless_present = "cdk-output")]
121121
lambda_execution_role_arn: Option<String>,
122+
123+
/// Lambda architecture
124+
#[clap(long, default_value = "x86_64")]
125+
pub(crate) architecture: crate::arch::Arch,
122126
}
123127

124128
#[derive(Debug, Eq, PartialEq)]
@@ -134,6 +138,7 @@ struct Options {
134138
lambda_test_s3_mrap_bucket_arn: String,
135139
lambda_test_s3_express_bucket_name: String,
136140
lambda_execution_role_arn: String,
141+
architecture: crate::arch::Arch,
137142
}
138143

139144
impl Options {
@@ -203,6 +208,7 @@ impl Options {
203208
lambda_test_s3_mrap_bucket_arn,
204209
lambda_test_s3_express_bucket_name,
205210
lambda_execution_role_arn,
211+
architecture: run_opt.architecture,
206212
})
207213
} else {
208214
Ok(Options {
@@ -223,6 +229,7 @@ impl Options {
223229
.lambda_test_s3_express_bucket_name
224230
.expect("required"),
225231
lambda_execution_role_arn: run_opt.lambda_execution_role_arn.expect("required"),
232+
architecture: run_opt.architecture,
226233
})
227234
}
228235
}
@@ -319,9 +326,17 @@ async fn run_canary(options: &Options, config: &aws_config::SdkConfig) -> Result
319326
"Creating the canary Lambda function named {}...",
320327
bundle_name
321328
);
329+
let function_name = format!(
330+
"{}-{}",
331+
bundle_name,
332+
match options.architecture {
333+
crate::arch::Arch::X86_64 => "x86_64",
334+
crate::arch::Arch::Aarch64 => "aarch64",
335+
}
336+
);
322337
create_lambda_fn(
323338
lambda_client.clone(),
324-
bundle_name,
339+
&function_name,
325340
bundle_file_name,
326341
options,
327342
)
@@ -330,11 +345,11 @@ async fn run_canary(options: &Options, config: &aws_config::SdkConfig) -> Result
330345

331346
info!("Invoking the canary Lambda...");
332347
let invoke_start_time = SystemTime::now();
333-
let invoke_result = invoke_lambda(lambda_client.clone(), bundle_name).await;
348+
let invoke_result = invoke_lambda(lambda_client.clone(), &function_name).await;
334349
let invoke_time = invoke_start_time.elapsed().expect("time in range");
335350

336351
info!("Deleting the canary Lambda...");
337-
delete_lambda_fn(lambda_client, bundle_name)
352+
delete_lambda_fn(lambda_client, &function_name)
338353
.await
339354
.context(here!())?;
340355

@@ -362,7 +377,7 @@ async fn build_bundle(options: &Options) -> Result<PathBuf> {
362377
sdk_release_tag: options.sdk_release_tag.clone(),
363378
sdk_path: options.sdk_path.clone(),
364379
musl: options.musl,
365-
architecture: crate::arch::Arch::X86_64,
380+
architecture: options.architecture,
366381
manifest_only: false,
367382
disable_jitter_entropy: true,
368383
feature_override: None,
@@ -433,10 +448,13 @@ async fn create_lambda_fn(
433448
),
434449
};
435450

451+
let lambda_arch: Architecture = options.architecture.into();
452+
let lambda_runtime: Runtime = options.architecture.into();
453+
436454
lambda_client
437455
.create_function()
438456
.function_name(bundle_name)
439-
.runtime(Runtime::Providedal2)
457+
.runtime(lambda_runtime)
440458
.role(&options.lambda_execution_role_arn)
441459
.handler("aws-sdk-rust-lambda-canary")
442460
.code(
@@ -449,6 +467,7 @@ async fn create_lambda_fn(
449467
.environment(env_builder.build())
450468
.timeout(180)
451469
.memory_size(options.lambda_function_memory_size_in_mb)
470+
.architectures(lambda_arch)
452471
.send()
453472
.await
454473
.context(here!("failed to create canary Lambda function"))?;
@@ -565,7 +584,8 @@ mod tests {
565584
lambda_test_s3_bucket_name: None,
566585
lambda_execution_role_arn: None,
567586
lambda_test_s3_mrap_bucket_arn: None,
568-
lambda_test_s3_express_bucket_name: None
587+
lambda_test_s3_express_bucket_name: None,
588+
architecture: crate::arch::Arch::X86_64,
569589
},
570590
RunArgs::try_parse_from([
571591
"run",
@@ -616,6 +636,7 @@ mod tests {
616636
lambda_test_s3_mrap_bucket_arn: "arn:aws:s3::000000000000:accesspoint/example.mrap"
617637
.to_owned(),
618638
lambda_test_s3_express_bucket_name: "test--usw2-az1--x-s3".to_owned(),
639+
architecture: crate::arch::Arch::X86_64,
619640
},
620641
Options::load_from(run_args).unwrap(),
621642
);

tools/ci-scripts/run-canary

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CANARY_STACK_CDK_OUTPUTS_BUCKET_NAME=$1
1010
export AWS_ACCESS_KEY_ID=$2
1111
export AWS_SECRET_ACCESS_KEY=$3
1212
export AWS_SESSION_TOKEN=$4
13+
ARCHITECTURE="${5:-x86_64}"
1314
export AWS_REGION=us-west-2
1415
export RUST_LOG=debug
1516

@@ -21,4 +22,5 @@ cargo run -- \
2122
run --rust-version "${RUST_STABLE_VERSION}" \
2223
--sdk-path "${SDK_PATH}" \
2324
--cdk-output cdk-outputs.json \
24-
--musl
25+
--musl \
26+
--architecture "${ARCHITECTURE}"

0 commit comments

Comments
 (0)