Loading tools/ci-cdk/.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -7,3 +7,4 @@ build # CDK asset staging directory .cdk.staging cdk.out cdk-outputs.json tools/ci-cdk/README.md +24 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,30 @@ continuous integration. The `cdk.json` file tells the CDK Toolkit how to synthesize the infrastructure. ## Canary local development Sometimes it's useful to only deploy the the canary resources to a test AWS account to iterate on the `canary-runner` and `canary-lambda`. To do this, run the following: ```bash npm install npm run build npx cdk --app "node build/bin/canary-only.js" synth npx cdk --app "node build/bin/canary-only.js" deploy --outputs-file cdk-outputs.json ``` From there, you can just point the `canary-runner` to the `cdk-outputs.json` to run it: ```bash cd canary-runner cargo run -- --sdk-version <version> --musl --cdk-outputs ../cdk-outputs.json ``` __NOTE:__ You may want to add a `--profile` to the deploy command to select a specific credential profile to deploy to if you don't want to use the default. Also, if this is a new test AWS account, be sure it CDK bootstrap it before attempting to deploy. ## Useful commands - `npm run lint`: lint code Loading tools/ci-cdk/bin/canary-only.ts 0 → 100644 +16 −0 Original line number Diff line number Diff line #!/usr/bin/env node /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ // This CDK app sets up the absolute minimum set of resources to succesfully // execute the canary with. import "source-map-support/register"; import { App } from "aws-cdk-lib"; import { CanaryStack } from "../lib/aws-sdk-rust/canary-stack"; const app = new App(); new CanaryStack(app, "aws-sdk-rust-canary-stack", {}); tools/ci-cdk/canary-runner/src/run.rs +77 −17 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ use clap::Parser; use cloudwatch::model::StandardUnit; use s3::ByteStream; use semver::Version; use serde::Deserialize; use smithy_rs_tool_common::git; use smithy_rs_tool_common::macros::here; use smithy_rs_tool_common::shell::ShellOperation; Loading Loading @@ -62,23 +63,82 @@ pub struct RunOpt { #[clap(long)] musl: bool, /// The name of the S3 bucket to upload the canary binary bundle to /// File path to a CDK outputs JSON file. This can be used instead /// of all the --lambda... args. #[clap(long)] lambda_code_s3_bucket_name: String, cdk_output: Option<PathBuf>, /// The name of the S3 bucket to upload the canary binary bundle to #[clap(long, required_unless_present = "cdk-output")] lambda_code_s3_bucket_name: Option<String>, /// The name of the S3 bucket for the canary Lambda to interact with #[clap(long)] lambda_test_s3_bucket_name: String, #[clap(long, required_unless_present = "cdk-output")] lambda_test_s3_bucket_name: Option<String>, /// The ARN of the role that the Lambda will execute as #[clap(long)] #[clap(long, required_unless_present = "cdk-output")] lambda_execution_role_arn: Option<String>, } #[derive(Debug)] struct Options { sdk_version: Option<String>, sdk_path: Option<PathBuf>, musl: bool, lambda_code_s3_bucket_name: String, lambda_test_s3_bucket_name: String, lambda_execution_role_arn: String, } impl Options { fn load_from(run_opt: RunOpt) -> Result<Options> { if let Some(cdk_output) = &run_opt.cdk_output { #[derive(Deserialize)] struct Inner { #[serde(rename = "canarycodebucketname")] lambda_code_s3_bucket_name: String, #[serde(rename = "canarytestbucketname")] lambda_test_s3_bucket_name: String, #[serde(rename = "lambdaexecutionrolearn")] lambda_execution_role_arn: String, } #[derive(Deserialize)] struct Outer { #[serde(rename = "aws-sdk-rust-canary-stack")] inner: Inner, } let value: Outer = serde_json::from_reader( std::fs::File::open(cdk_output).context("open cdk output")?, ) .context("read cdk output")?; Ok(Options { sdk_version: run_opt.sdk_version, sdk_path: run_opt.sdk_path, musl: run_opt.musl, lambda_code_s3_bucket_name: value.inner.lambda_code_s3_bucket_name, lambda_test_s3_bucket_name: value.inner.lambda_test_s3_bucket_name, lambda_execution_role_arn: value.inner.lambda_execution_role_arn, }) } else { Ok(Options { sdk_version: run_opt.sdk_version, sdk_path: run_opt.sdk_path, musl: run_opt.musl, lambda_code_s3_bucket_name: run_opt.lambda_code_s3_bucket_name.expect("required"), lambda_test_s3_bucket_name: run_opt.lambda_test_s3_bucket_name.expect("required"), lambda_execution_role_arn: run_opt.lambda_execution_role_arn.expect("required"), }) } } } pub async fn run(opt: RunOpt) -> Result<()> { let options = Options::load_from(opt)?; let start_time = SystemTime::now(); let config = aws_config::load_from_env().await; let result = run_canary(opt, &config).await; let result = run_canary(&options, &config).await; let mut metrics = vec![ ( Loading Loading @@ -129,19 +189,19 @@ pub async fn run(opt: RunOpt) -> Result<()> { result.map(|_| ()) } async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration> { async fn run_canary(options: &Options, config: &aws_config::Config) -> Result<Duration> { let repo_root = git_root().await?; env::set_current_dir(repo_root.join("tools/ci-cdk/canary-lambda")) .context("failed to change working directory")?; if let Some(sdk_version) = &opt.sdk_version { if let Some(sdk_version) = &options.sdk_version { use_correct_revision(sdk_version) .await .context(here!("failed to select correct revision of smithy-rs"))?; } info!("Building the canary..."); let bundle_path = build_bundle(&opt).await?; let bundle_path = build_bundle(options).await?; let bundle_file_name = bundle_path.file_name().unwrap().to_str().unwrap(); let bundle_name = bundle_path.file_stem().unwrap().to_str().unwrap(); Loading @@ -151,7 +211,7 @@ async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration info!("Uploading Lambda code bundle to S3..."); upload_bundle( s3_client, &opt.lambda_code_s3_bucket_name, &options.lambda_code_s3_bucket_name, bundle_file_name, &bundle_path, ) Loading @@ -166,9 +226,9 @@ async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration lambda_client.clone(), bundle_name, bundle_file_name, &opt.lambda_execution_role_arn, &opt.lambda_code_s3_bucket_name, &opt.lambda_test_s3_bucket_name, &options.lambda_execution_role_arn, &options.lambda_code_s3_bucket_name, &options.lambda_test_s3_bucket_name, ) .await .context(here!())?; Loading Loading @@ -208,15 +268,15 @@ async fn use_correct_revision(sdk_version: &str) -> Result<()> { } /// Returns the path to the compiled bundle zip file async fn build_bundle(opt: &RunOpt) -> Result<PathBuf> { async fn build_bundle(options: &Options) -> Result<PathBuf> { let mut builder = Command::new("./build-bundle"); if let Some(sdk_version) = &opt.sdk_version { if let Some(sdk_version) = &options.sdk_version { builder.arg("--sdk-version").arg(sdk_version); } if let Some(sdk_path) = &opt.sdk_path { if let Some(sdk_path) = &options.sdk_path { builder.arg("--sdk-path").arg(sdk_path); } if opt.musl { if options.musl { builder.arg("--musl"); } let output = builder Loading tools/ci-cdk/lib/aws-sdk-rust/canary-stack.ts +79 −48 Original line number Diff line number Diff line Loading @@ -11,26 +11,31 @@ import { ServicePrincipal, } from "aws-cdk-lib/aws-iam"; import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3"; import { StackProps, Stack, Tags, RemovalPolicy, Duration } from "aws-cdk-lib"; import { StackProps, Stack, Tags, RemovalPolicy, Duration, CfnOutput } from "aws-cdk-lib"; import { Construct } from "constructs"; import { GitHubOidcRole } from "../constructs/github-oidc-role"; export interface Properties extends StackProps { githubActionsOidcProvider: OpenIdConnectProvider; githubActionsOidcProvider?: OpenIdConnectProvider; } export class CanaryStack extends Stack { public readonly awsSdkRustOidcRole: GitHubOidcRole; public readonly awsSdkRustOidcRole?: GitHubOidcRole; public readonly lambdaExecutionRole: Role; public readonly canaryCodeBucket: Bucket; public readonly canaryTestBucket: Bucket; public readonly lambdaExecutionRoleArn: CfnOutput; public readonly canaryCodeBucketName: CfnOutput; public readonly canaryTestBucketName: CfnOutput; constructor(scope: Construct, id: string, props: Properties) { super(scope, id, props); // Tag the resources created by this stack to make identifying resources easier Tags.of(this).add("stack", id); if (props.githubActionsOidcProvider) { this.awsSdkRustOidcRole = new GitHubOidcRole(this, "aws-sdk-rust", { name: "aws-sdk-rust-canary", githubOrg: "awslabs", Loading Loading @@ -61,6 +66,7 @@ export class CanaryStack extends Stack { resources: ["*"], }), ); } // Create S3 bucket to upload canary Lambda code into this.canaryCodeBucket = new Bucket(this, "canary-code-bucket", { Loading @@ -77,9 +83,18 @@ export class CanaryStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); // Output the bucket name to make it easier to invoke the canary runner this.canaryCodeBucketName = new CfnOutput(this, "canary-code-bucket-name", { value: this.canaryCodeBucket.bucketName, description: "Name of the canary code bucket", exportName: "canaryCodeBucket", }); // Allow the OIDC role to GetObject and PutObject to the code bucket if (this.awsSdkRustOidcRole) { this.canaryCodeBucket.grantRead(this.awsSdkRustOidcRole.oidcRole); this.canaryCodeBucket.grantWrite(this.awsSdkRustOidcRole.oidcRole); } // Create S3 bucket for the canaries to talk to this.canaryTestBucket = new Bucket(this, "canary-test-bucket", { Loading @@ -96,12 +111,26 @@ export class CanaryStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); // Output the bucket name to make it easier to invoke the canary runner this.canaryTestBucketName = new CfnOutput(this, "canary-test-bucket-name", { value: this.canaryTestBucket.bucketName, description: "Name of the canary test bucket", exportName: "canaryTestBucket", }); // Create a role for the canary Lambdas to assume this.lambdaExecutionRole = new Role(this, "lambda-execution-role", { roleName: "aws-sdk-rust-canary-lambda-exec-role", assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); // Output the Lambda execution role ARN to make it easier to invoke the canary runner this.lambdaExecutionRoleArn = new CfnOutput(this, "lambda-execution-role-arn", { value: this.lambdaExecutionRole.roleArn, description: "Canary Lambda execution role ARN", exportName: "canaryLambdaExecutionRoleArn", }); // Allow canaries to write logs to CloudWatch this.lambdaExecutionRole.addToPolicy( new PolicyStatement({ Loading @@ -124,6 +153,7 @@ export class CanaryStack extends Stack { ); // Allow the OIDC role to pass the Lambda execution role to Lambda if (this.awsSdkRustOidcRole) { this.awsSdkRustOidcRole.oidcRole.addToPolicy( new PolicyStatement({ actions: ["iam:PassRole"], Loading @@ -140,3 +170,4 @@ export class CanaryStack extends Stack { ); } } } Loading
tools/ci-cdk/.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -7,3 +7,4 @@ build # CDK asset staging directory .cdk.staging cdk.out cdk-outputs.json
tools/ci-cdk/README.md +24 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,30 @@ continuous integration. The `cdk.json` file tells the CDK Toolkit how to synthesize the infrastructure. ## Canary local development Sometimes it's useful to only deploy the the canary resources to a test AWS account to iterate on the `canary-runner` and `canary-lambda`. To do this, run the following: ```bash npm install npm run build npx cdk --app "node build/bin/canary-only.js" synth npx cdk --app "node build/bin/canary-only.js" deploy --outputs-file cdk-outputs.json ``` From there, you can just point the `canary-runner` to the `cdk-outputs.json` to run it: ```bash cd canary-runner cargo run -- --sdk-version <version> --musl --cdk-outputs ../cdk-outputs.json ``` __NOTE:__ You may want to add a `--profile` to the deploy command to select a specific credential profile to deploy to if you don't want to use the default. Also, if this is a new test AWS account, be sure it CDK bootstrap it before attempting to deploy. ## Useful commands - `npm run lint`: lint code Loading
tools/ci-cdk/bin/canary-only.ts 0 → 100644 +16 −0 Original line number Diff line number Diff line #!/usr/bin/env node /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ // This CDK app sets up the absolute minimum set of resources to succesfully // execute the canary with. import "source-map-support/register"; import { App } from "aws-cdk-lib"; import { CanaryStack } from "../lib/aws-sdk-rust/canary-stack"; const app = new App(); new CanaryStack(app, "aws-sdk-rust-canary-stack", {});
tools/ci-cdk/canary-runner/src/run.rs +77 −17 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ use clap::Parser; use cloudwatch::model::StandardUnit; use s3::ByteStream; use semver::Version; use serde::Deserialize; use smithy_rs_tool_common::git; use smithy_rs_tool_common::macros::here; use smithy_rs_tool_common::shell::ShellOperation; Loading Loading @@ -62,23 +63,82 @@ pub struct RunOpt { #[clap(long)] musl: bool, /// The name of the S3 bucket to upload the canary binary bundle to /// File path to a CDK outputs JSON file. This can be used instead /// of all the --lambda... args. #[clap(long)] lambda_code_s3_bucket_name: String, cdk_output: Option<PathBuf>, /// The name of the S3 bucket to upload the canary binary bundle to #[clap(long, required_unless_present = "cdk-output")] lambda_code_s3_bucket_name: Option<String>, /// The name of the S3 bucket for the canary Lambda to interact with #[clap(long)] lambda_test_s3_bucket_name: String, #[clap(long, required_unless_present = "cdk-output")] lambda_test_s3_bucket_name: Option<String>, /// The ARN of the role that the Lambda will execute as #[clap(long)] #[clap(long, required_unless_present = "cdk-output")] lambda_execution_role_arn: Option<String>, } #[derive(Debug)] struct Options { sdk_version: Option<String>, sdk_path: Option<PathBuf>, musl: bool, lambda_code_s3_bucket_name: String, lambda_test_s3_bucket_name: String, lambda_execution_role_arn: String, } impl Options { fn load_from(run_opt: RunOpt) -> Result<Options> { if let Some(cdk_output) = &run_opt.cdk_output { #[derive(Deserialize)] struct Inner { #[serde(rename = "canarycodebucketname")] lambda_code_s3_bucket_name: String, #[serde(rename = "canarytestbucketname")] lambda_test_s3_bucket_name: String, #[serde(rename = "lambdaexecutionrolearn")] lambda_execution_role_arn: String, } #[derive(Deserialize)] struct Outer { #[serde(rename = "aws-sdk-rust-canary-stack")] inner: Inner, } let value: Outer = serde_json::from_reader( std::fs::File::open(cdk_output).context("open cdk output")?, ) .context("read cdk output")?; Ok(Options { sdk_version: run_opt.sdk_version, sdk_path: run_opt.sdk_path, musl: run_opt.musl, lambda_code_s3_bucket_name: value.inner.lambda_code_s3_bucket_name, lambda_test_s3_bucket_name: value.inner.lambda_test_s3_bucket_name, lambda_execution_role_arn: value.inner.lambda_execution_role_arn, }) } else { Ok(Options { sdk_version: run_opt.sdk_version, sdk_path: run_opt.sdk_path, musl: run_opt.musl, lambda_code_s3_bucket_name: run_opt.lambda_code_s3_bucket_name.expect("required"), lambda_test_s3_bucket_name: run_opt.lambda_test_s3_bucket_name.expect("required"), lambda_execution_role_arn: run_opt.lambda_execution_role_arn.expect("required"), }) } } } pub async fn run(opt: RunOpt) -> Result<()> { let options = Options::load_from(opt)?; let start_time = SystemTime::now(); let config = aws_config::load_from_env().await; let result = run_canary(opt, &config).await; let result = run_canary(&options, &config).await; let mut metrics = vec![ ( Loading Loading @@ -129,19 +189,19 @@ pub async fn run(opt: RunOpt) -> Result<()> { result.map(|_| ()) } async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration> { async fn run_canary(options: &Options, config: &aws_config::Config) -> Result<Duration> { let repo_root = git_root().await?; env::set_current_dir(repo_root.join("tools/ci-cdk/canary-lambda")) .context("failed to change working directory")?; if let Some(sdk_version) = &opt.sdk_version { if let Some(sdk_version) = &options.sdk_version { use_correct_revision(sdk_version) .await .context(here!("failed to select correct revision of smithy-rs"))?; } info!("Building the canary..."); let bundle_path = build_bundle(&opt).await?; let bundle_path = build_bundle(options).await?; let bundle_file_name = bundle_path.file_name().unwrap().to_str().unwrap(); let bundle_name = bundle_path.file_stem().unwrap().to_str().unwrap(); Loading @@ -151,7 +211,7 @@ async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration info!("Uploading Lambda code bundle to S3..."); upload_bundle( s3_client, &opt.lambda_code_s3_bucket_name, &options.lambda_code_s3_bucket_name, bundle_file_name, &bundle_path, ) Loading @@ -166,9 +226,9 @@ async fn run_canary(opt: RunOpt, config: &aws_config::Config) -> Result<Duration lambda_client.clone(), bundle_name, bundle_file_name, &opt.lambda_execution_role_arn, &opt.lambda_code_s3_bucket_name, &opt.lambda_test_s3_bucket_name, &options.lambda_execution_role_arn, &options.lambda_code_s3_bucket_name, &options.lambda_test_s3_bucket_name, ) .await .context(here!())?; Loading Loading @@ -208,15 +268,15 @@ async fn use_correct_revision(sdk_version: &str) -> Result<()> { } /// Returns the path to the compiled bundle zip file async fn build_bundle(opt: &RunOpt) -> Result<PathBuf> { async fn build_bundle(options: &Options) -> Result<PathBuf> { let mut builder = Command::new("./build-bundle"); if let Some(sdk_version) = &opt.sdk_version { if let Some(sdk_version) = &options.sdk_version { builder.arg("--sdk-version").arg(sdk_version); } if let Some(sdk_path) = &opt.sdk_path { if let Some(sdk_path) = &options.sdk_path { builder.arg("--sdk-path").arg(sdk_path); } if opt.musl { if options.musl { builder.arg("--musl"); } let output = builder Loading
tools/ci-cdk/lib/aws-sdk-rust/canary-stack.ts +79 −48 Original line number Diff line number Diff line Loading @@ -11,26 +11,31 @@ import { ServicePrincipal, } from "aws-cdk-lib/aws-iam"; import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3"; import { StackProps, Stack, Tags, RemovalPolicy, Duration } from "aws-cdk-lib"; import { StackProps, Stack, Tags, RemovalPolicy, Duration, CfnOutput } from "aws-cdk-lib"; import { Construct } from "constructs"; import { GitHubOidcRole } from "../constructs/github-oidc-role"; export interface Properties extends StackProps { githubActionsOidcProvider: OpenIdConnectProvider; githubActionsOidcProvider?: OpenIdConnectProvider; } export class CanaryStack extends Stack { public readonly awsSdkRustOidcRole: GitHubOidcRole; public readonly awsSdkRustOidcRole?: GitHubOidcRole; public readonly lambdaExecutionRole: Role; public readonly canaryCodeBucket: Bucket; public readonly canaryTestBucket: Bucket; public readonly lambdaExecutionRoleArn: CfnOutput; public readonly canaryCodeBucketName: CfnOutput; public readonly canaryTestBucketName: CfnOutput; constructor(scope: Construct, id: string, props: Properties) { super(scope, id, props); // Tag the resources created by this stack to make identifying resources easier Tags.of(this).add("stack", id); if (props.githubActionsOidcProvider) { this.awsSdkRustOidcRole = new GitHubOidcRole(this, "aws-sdk-rust", { name: "aws-sdk-rust-canary", githubOrg: "awslabs", Loading Loading @@ -61,6 +66,7 @@ export class CanaryStack extends Stack { resources: ["*"], }), ); } // Create S3 bucket to upload canary Lambda code into this.canaryCodeBucket = new Bucket(this, "canary-code-bucket", { Loading @@ -77,9 +83,18 @@ export class CanaryStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); // Output the bucket name to make it easier to invoke the canary runner this.canaryCodeBucketName = new CfnOutput(this, "canary-code-bucket-name", { value: this.canaryCodeBucket.bucketName, description: "Name of the canary code bucket", exportName: "canaryCodeBucket", }); // Allow the OIDC role to GetObject and PutObject to the code bucket if (this.awsSdkRustOidcRole) { this.canaryCodeBucket.grantRead(this.awsSdkRustOidcRole.oidcRole); this.canaryCodeBucket.grantWrite(this.awsSdkRustOidcRole.oidcRole); } // Create S3 bucket for the canaries to talk to this.canaryTestBucket = new Bucket(this, "canary-test-bucket", { Loading @@ -96,12 +111,26 @@ export class CanaryStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); // Output the bucket name to make it easier to invoke the canary runner this.canaryTestBucketName = new CfnOutput(this, "canary-test-bucket-name", { value: this.canaryTestBucket.bucketName, description: "Name of the canary test bucket", exportName: "canaryTestBucket", }); // Create a role for the canary Lambdas to assume this.lambdaExecutionRole = new Role(this, "lambda-execution-role", { roleName: "aws-sdk-rust-canary-lambda-exec-role", assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); // Output the Lambda execution role ARN to make it easier to invoke the canary runner this.lambdaExecutionRoleArn = new CfnOutput(this, "lambda-execution-role-arn", { value: this.lambdaExecutionRole.roleArn, description: "Canary Lambda execution role ARN", exportName: "canaryLambdaExecutionRoleArn", }); // Allow canaries to write logs to CloudWatch this.lambdaExecutionRole.addToPolicy( new PolicyStatement({ Loading @@ -124,6 +153,7 @@ export class CanaryStack extends Stack { ); // Allow the OIDC role to pass the Lambda execution role to Lambda if (this.awsSdkRustOidcRole) { this.awsSdkRustOidcRole.oidcRole.addToPolicy( new PolicyStatement({ actions: ["iam:PassRole"], Loading @@ -140,3 +170,4 @@ export class CanaryStack extends Stack { ); } } }