diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml new file mode 100644 index 00000000..f1e30d09 --- /dev/null +++ b/.github/workflows/run-integration-test.yml @@ -0,0 +1,62 @@ +name: Run integration tests + +permissions: + id-token: write + contents: read + +on: + workflow_dispatch: + push: + +jobs: + run-integration-tests: + runs-on: ubuntu-latest + steps: + - name: install Cargo Lambda + uses: jaxxstorm/action-install-gh-release@v1.9.0 + with: + repo: cargo-lambda/cargo-lambda + platform: linux + arch: x86_64 + - name: install Zig toolchain + uses: korandoru/setup-zig@v1 + with: + zig-version: 0.10.0 + - name: install SAM + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + - uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ secrets.ROLE_SESSION_NAME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: build stack + run: cd lambda-integration-tests && sam build --beta-features + - name: validate stack + run: cd lambda-integration-tests && sam validate --lint + - name: deploy stack + id: deploy_stack + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd lambda-integration-tests + stackName="aws-lambda-rust-integ-test-$GITHUB_RUN_ID" + echo "STACK_NAME=$stackName" >> "$GITHUB_OUTPUT" + echo "Stack name = $stackName" + sam deploy --stack-name "${stackName}" --parameter-overrides "ParameterKey=SecretToken,ParameterValue=${{ secrets.SECRET_TOKEN }}" "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }}" --no-confirm-changeset --no-progressbar > disable_output + TEST_ENDPOINT=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | .OutputValue') + echo "TEST_ENDPOINT=$TEST_ENDPOINT" >> "$GITHUB_OUTPUT" + - name: run test + env: + SECRET_TOKEN: ${{ secrets.SECRET_TOKEN }} + TEST_ENDPOINT: ${{ steps.deploy_stack.outputs.TEST_ENDPOINT }} + run: cd lambda-integration-tests && cargo test + - name: cleanup + if: always() + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + STACK_NAME: ${{ steps.deploy_stack.outputs.STACK_NAME }} + run: sam delete --stack-name "${STACK_NAME}" --no-prompts diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 1b0fc3ef..ee44a969 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "lambda_integration_tests" -version = "0.5.0" -authors = ["Nicolas Moutschen "] -edition = "2018" +name = "aws_lambda_rust_integration_tests" +version = "0.1.0" +authors = ["Maxime David"] +edition = "2021" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" @@ -10,13 +10,21 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "../README.md" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -lambda_http = { path = "../lambda-http" } lambda_runtime = { path = "../lambda-runtime" } -lambda-extension = { path = "../lambda-extension" } -serde = { version = "1", features = ["derive"] } +aws_lambda_events = { path = "../lambda-events" } +serde_json = "1.0.121" tokio = { version = "1", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +serde = { version = "1.0.204", features = ["derive"] } + +[dev-dependencies] +reqwest = { version = "0.12.5", features = ["blocking"] } +openssl = { version = "0.10", features = ["vendored"] } + +[[bin]] +name = "helloworld" +path = "src/helloworld.rs" + +[[bin]] +name = "authorizer" +path = "src/authorizer.rs" diff --git a/lambda-integration-tests/python/main.py b/lambda-integration-tests/python/main.py deleted file mode 100644 index e7e2114b..00000000 --- a/lambda-integration-tests/python/main.py +++ /dev/null @@ -1,4 +0,0 @@ -def handler(event, context): - return { - "message": event["command"].upper() - } \ No newline at end of file diff --git a/lambda-integration-tests/samconfig.toml b/lambda-integration-tests/samconfig.toml new file mode 100644 index 00000000..0212b62a --- /dev/null +++ b/lambda-integration-tests/samconfig.toml @@ -0,0 +1,23 @@ +version = 0.1 + +[default] +[default.build.parameters] +cached = true +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +s3_bucket = "aws-lambda-rust-runtime-integration-testing" + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" \ No newline at end of file diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs new file mode 100644 index 00000000..41ddd2d8 --- /dev/null +++ b/lambda-integration-tests/src/authorizer.rs @@ -0,0 +1,53 @@ +use std::env; + +use aws_lambda_events::{ + apigw::{ApiGatewayCustomAuthorizerPolicy, ApiGatewayCustomAuthorizerResponse}, + event::iam::IamPolicyStatement, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; +use serde::Deserialize; +use serde_json::json; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct APIGatewayCustomAuthorizerRequest { + authorization_token: String, + method_arn: String, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func( + event: LambdaEvent, +) -> Result { + let expected_token = env::var("SECRET_TOKEN").expect("could not read the secret token"); + if event.payload.authorization_token == expected_token { + return Ok(allow(&event.payload.method_arn)); + } + panic!("token is not valid"); +} + +fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { + let stmt = IamPolicyStatement { + action: vec!["execute-api:Invoke".to_string()], + resource: vec![method_arn.to_owned()], + effect: aws_lambda_events::iam::IamPolicyEffect::Allow, + condition: None, + }; + let policy = ApiGatewayCustomAuthorizerPolicy { + version: Some("2012-10-17".to_string()), + statement: vec![stmt], + }; + ApiGatewayCustomAuthorizerResponse { + principal_id: Some("user".to_owned()), + policy_document: policy, + context: json!({ "hello": "world" }), + usage_identifier_key: None, + } +} diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs deleted file mode 100644 index 5e9ec553..00000000 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ /dev/null @@ -1,28 +0,0 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; -use tracing::info; - -async fn my_extension(event: LambdaEvent) -> Result<(), Error> { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension-fn] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - info!("[extension-fn] Request event received: {:?}", e); - } - } - - Ok(()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(service_fn(my_extension)).await -} diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs deleted file mode 100644 index e2c73fa3..00000000 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; -use tracing::info; - -struct MyExtension { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyExtension { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyExtension { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyExtension { - type Error = Error; - type Future = Pin>>>; - type Response = (); - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[extension] Service was already ready"); - } else { - info!("[extension] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, event: LambdaEvent) -> Self::Future { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - self.invoke_count += 1; - info!("[extension] Request event {} received: {:?}", self.invoke_count, e); - } - } - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[extension] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[extension] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(MyExtension::default()).await -} diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs deleted file mode 100644 index 8107f423..00000000 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ /dev/null @@ -1,26 +0,0 @@ -use lambda_http::{ext::RequestExt, service_fn, Body, Error, IntoResponse, Request, Response}; -use tracing::info; - -async fn handler(event: Request) -> Result { - let _context = event.lambda_context(); - info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); - - Ok(Response::builder() - .status(200) - .body(Body::from("Hello, world!")) - .unwrap()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let handler = service_fn(handler); - lambda_http::run(handler).await -} diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs deleted file mode 100644 index d8e6f74f..00000000 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_http::{ext::RequestExt, Body, Error, Request, Response, Service}; -use tracing::info; - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyHandler { - type Error = Error; - type Future = Pin> + Send>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[http-trait] Service was already ready"); - } else { - info!("[http-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: Request) -> Self::Future { - self.invoke_count += 1; - info!("[http-trait] Received event {}: {:?}", self.invoke_count, request); - info!("[http-trait] Lambda context: {:?}", request.lambda_context()); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[http-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[http-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response::builder() - .status(200) - .body(Body::from("Hello, World!")) - .unwrap()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_http::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs deleted file mode 100644 index b474bc8d..00000000 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ /dev/null @@ -1,75 +0,0 @@ -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; -use std::{ - future::{ready, Future}, - pin::Pin, - sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, - }, - task::Poll, -}; -use tracing::info; - -/// Custom log processor that increments a counter for each log record. -/// -/// This is a simple example of a custom log processor that can be used to -/// count the number of log records that are processed. -/// -/// This needs to derive Clone (and store the counter in an Arc) as the runtime -/// could need multiple `Service`s to process the logs. -#[derive(Clone, Default)] -struct MyLogsProcessor { - counter: Arc, -} - -impl MyLogsProcessor { - pub fn new() -> Self { - Self::default() - } -} - -type MyLogsFuture = Pin> + Send>>; - -/// Implementation of the actual log processor -/// -/// This receives a `Vec` whenever there are new log entries available. -impl Service> for MyLogsProcessor { - type Response = (); - type Error = Error; - type Future = MyLogsFuture; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, logs: Vec) -> Self::Future { - let counter = self.counter.fetch_add(1, SeqCst); - for log in logs { - match log.record { - LambdaLogRecord::Function(record) => { - info!("[logs] {} [function] {}: {}", log.time, counter, record.trim()) - } - LambdaLogRecord::Extension(record) => { - info!("[logs] {} [extension] {}: {}", log.time, counter, record.trim()) - } - _ => (), - } - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let logs_processor = SharedService::new(MyLogsProcessor::new()); - Extension::new().with_logs_processor(logs_processor).run().await?; - - Ok(()) -} diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs deleted file mode 100644 index d16717aa..00000000 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ /dev/null @@ -1,36 +0,0 @@ -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -async fn handler(event: LambdaEvent) -> Result { - info!("[handler-fn] Received event: {:?}", event); - - let (event, _) = event.into_parts(); - - Ok(Response { - message: event.command.to_uppercase(), - }) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(service_fn(handler)).await -} diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs deleted file mode 100644 index 0bf31e43..00000000 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_runtime::{Error, LambdaEvent, Service}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service> for MyHandler { - type Error = Error; - type Future = Pin>>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[runtime-trait] Service was already ready"); - } else { - info!("[runtime-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: LambdaEvent) -> Self::Future { - self.invoke_count += 1; - info!("[runtime-trait] Received event {}: {:?}", self.invoke_count, request); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[runtime-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[runtime-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response { - message: request.payload.command.to_uppercase(), - }))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs new file mode 100644 index 00000000..9989da43 --- /dev/null +++ b/lambda-integration-tests/src/helloworld.rs @@ -0,0 +1,26 @@ +use aws_lambda_events::{ + apigw::{ApiGatewayProxyRequest, ApiGatewayProxyResponse}, + http::HeaderMap, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(_event: LambdaEvent) -> Result { + let mut headers = HeaderMap::new(); + headers.insert("content-type", "text/html".parse().unwrap()); + let resp = ApiGatewayProxyResponse { + status_code: 200, + multi_value_headers: headers.clone(), + is_base64_encoded: false, + body: Some("Hello world!".into()), + headers, + }; + Ok(resp) +} diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index d148c264..1aa69fc8 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -1,325 +1,62 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 +Description: maxday-test + +Parameters: + LambdaRole: + Type: String + SecretToken: + Type: String Globals: Function: - MemorySize: 128 - Handler: bootstrap - Timeout: 5 + Timeout: 3 Resources: - # Rust function using runtime_fn running on AL2023 - RuntimeFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL2 - RuntimeFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL1 - RuntimeFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2023 - RuntimeTraitAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2 - RuntimeTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL1 - RuntimeTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL2023 - HttpFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ + API: + Type: AWS::Serverless::Api + Properties: + StageName: integ-test + Auth: + DefaultAuthorizer: MyLambdaAuthorizer + Authorizers: + MyLambdaAuthorizer: + FunctionArn: !GetAtt AuthorizerFunction.Arn + HelloWorldFunction: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: helloworld + Properties: + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 + Role: !Ref LambdaRole Events: - ApiGet: + HelloWorld: Type: Api Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait + RestApiId: !Ref API + Path: /hello + Method: get - # Rust function using lambda_http::service_fn running on AL2 - HttpFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2023 - HttpTraitAl2023: + AuthorizerFunction: Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: authorizer Properties: - CodeUri: ../build/http-trait/ + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2 - HttpTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL1 - HttpFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL1 - HttpTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL2 - PythonAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.9 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL1 - Python: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.7 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - LogsTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/logs-trait/ - - ExtensionFn: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-fn/ - - ExtensionTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-trait/ + Role: !Ref LambdaRole + Environment: + Variables: + SECRET_TOKEN: !Ref SecretToken Outputs: - RuntimeFnAl2: - Value: !GetAtt RuntimeFnAl2.Arn - RuntimeFn: - Value: !GetAtt RuntimeFn.Arn - RuntimeTraitAl2: - Value: !GetAtt RuntimeTraitAl2.Arn - RuntimeTrait: - Value: !GetAtt RuntimeTrait.Arn - PythonAl2: - Value: !GetAtt PythonAl2.Arn - Python: - Value: !GetAtt Python.Arn - - RestApiUrl: - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod" - HttpApiUrl: - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" \ No newline at end of file + HelloApiEndpoint: + Description: "API Gateway endpoint URL for HelloWorld" + Value: !Sub "https://${API}.execute-api.${AWS::Region}.amazonaws.com/integ-test/hello/" \ No newline at end of file diff --git a/lambda-integration-tests/tests/integration_test.rs b/lambda-integration-tests/tests/integration_test.rs new file mode 100644 index 00000000..557b8e0d --- /dev/null +++ b/lambda-integration-tests/tests/integration_test.rs @@ -0,0 +1,12 @@ +#[test] +fn test_calling_lambda_should_return_200() { + let test_endpoint = std::env::var("TEST_ENDPOINT").expect("could not read TEST_ENDPOINT"); + let secret_token = std::env::var("SECRET_TOKEN").expect("could not read SECRET_TOKEN"); + let client = reqwest::blocking::Client::new(); + let res = client + .get(test_endpoint) + .header("Authorization", secret_token) + .send() + .expect("could not the request"); + assert_eq!(res.status(), 200); +}