From 07ca37c8bda4aed4ca96bd569576db7dc519e5a5 Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Wed, 20 Nov 2024 17:10:43 -0500 Subject: [PATCH 1/2] Add ability to create new canary deployment --- src/adapters/ingresses/apig.rs | 70 ++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/adapters/ingresses/apig.rs b/src/adapters/ingresses/apig.rs index 396b5ce..9721bce 100644 --- a/src/adapters/ingresses/apig.rs +++ b/src/adapters/ingresses/apig.rs @@ -7,8 +7,8 @@ use async_trait::async_trait; use miette::{IntoDiagnostic, Result}; use tokio::{fs::File, io::AsyncReadExt}; -use aws_sdk_apigateway::client::Client as GatewayClient; -use aws_sdk_lambda::client::Client as LambdaClient; +use aws_sdk_apigateway::{client::Client as GatewayClient, types::DeploymentCanarySettings}; +use aws_sdk_lambda::{client::Client as LambdaClient, primitives::Blob, types::FunctionCode}; /// AwsApiGateway is the Ingress implementation for AWS API Gateway + Lambda. /// It's responsible for creating canary deployments on API Gateway, updating their @@ -37,6 +37,61 @@ impl AwsApiGateway { lambda_client, }) } + + pub async fn upload_lambda(&self, lambda_name: &str) -> Result { + // Parse the bytes into the format AWS wants + let code = Blob::from(self.lambda_artifact.clone()); + + // Turn it into an uploadable zip file + let function_code = FunctionCode::builder().zip_file(code).build(); + + // Upload it to Lambda + let res = self + .lambda_client + .update_function_code() + .function_name(lambda_name) + .zip_file(function_code.zip_file().unwrap().clone()) + .send() + .await?; + + Ok(res.version().unwrap().to_string()) + } + + pub async fn create_apig_deployment( + &self, + api_id: &str, + stage_name: &str, + lambda_name: &str, + lambda_version: &str, + traffic_percentage: f64, + ) -> Result<(), aws_sdk_apigateway::Error> { + // Update the APIG with the new lambda version + self.apig_client + .put_integration() + .rest_api_id(api_id) + .uri(format!( + "arn:aws:lambda:us-east-2:471112630982:function:{}:{}", + lambda_name, lambda_version + )) + .send() + .await?; + + // Create a deployment with canary settings to deploy our new lambda + self.apig_client + .create_deployment() + .rest_api_id(api_id) + .stage_name(stage_name) + .canary_settings( + DeploymentCanarySettings::builder() + .percent_traffic(traffic_percentage) + .use_stage_cache(false) + .build(), + ) + .send() + .await?; + + Ok(()) + } } /// given a path to a file, load it as an array of bytes. @@ -51,6 +106,15 @@ async fn read_file(artifact_path: PathBuf) -> Result> { #[async_trait] impl Ingress for AwsApiGateway { async fn deploy(&mut self) -> Result<()> { - todo!() + // First, we need to deploy the new version of the lambda + let lambda_version = self.upload_lambda("releases").await.unwrap(); + + // Next, we need to create a new deployment, pointing at our + // new lambda version with canary settings + self.create_apig_deployment("Releases", "prod", "releases", &lambda_version, 10.0) + .await + .unwrap(); + + Ok(()) } } From af20baa7bd93acdc1e5afc05114d535341b881b8 Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Thu, 21 Nov 2024 13:35:45 -0500 Subject: [PATCH 2/2] Fix correct error handling --- src/adapters/ingresses/apig.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/adapters/ingresses/apig.rs b/src/adapters/ingresses/apig.rs index 9721bce..3cba1c5 100644 --- a/src/adapters/ingresses/apig.rs +++ b/src/adapters/ingresses/apig.rs @@ -4,6 +4,7 @@ use crate::utils::load_default_aws_config; use super::Ingress; use async_trait::async_trait; +use miette::miette; use miette::{IntoDiagnostic, Result}; use tokio::{fs::File, io::AsyncReadExt}; @@ -31,6 +32,7 @@ impl AwsApiGateway { let config = load_default_aws_config().await; let apig_client = GatewayClient::new(config); let lambda_client = LambdaClient::new(config); + Ok(Self { lambda_artifact: artifact, apig_client, @@ -38,23 +40,31 @@ impl AwsApiGateway { }) } - pub async fn upload_lambda(&self, lambda_name: &str) -> Result { + pub async fn upload_lambda(&self, lambda_name: &str) -> Result { // Parse the bytes into the format AWS wants let code = Blob::from(self.lambda_artifact.clone()); // Turn it into an uploadable zip file let function_code = FunctionCode::builder().zip_file(code).build(); + let zip_file = function_code + .zip_file() + .ok_or(miette!("Couldn't zip lambda code"))?; // Upload it to Lambda let res = self .lambda_client .update_function_code() .function_name(lambda_name) - .zip_file(function_code.zip_file().unwrap().clone()) + .zip_file(zip_file.clone()) .send() - .await?; + .await + .into_diagnostic()?; - Ok(res.version().unwrap().to_string()) + let version = res + .version() + .ok_or(miette!("Couldn't get version of deployed lambda"))?; + + Ok(version.to_string()) } pub async fn create_apig_deployment( @@ -64,7 +74,7 @@ impl AwsApiGateway { lambda_name: &str, lambda_version: &str, traffic_percentage: f64, - ) -> Result<(), aws_sdk_apigateway::Error> { + ) -> Result<()> { // Update the APIG with the new lambda version self.apig_client .put_integration() @@ -74,7 +84,8 @@ impl AwsApiGateway { lambda_name, lambda_version )) .send() - .await?; + .await + .into_diagnostic()?; // Create a deployment with canary settings to deploy our new lambda self.apig_client @@ -88,7 +99,8 @@ impl AwsApiGateway { .build(), ) .send() - .await?; + .await + .into_diagnostic()?; Ok(()) } @@ -107,13 +119,12 @@ async fn read_file(artifact_path: PathBuf) -> Result> { impl Ingress for AwsApiGateway { async fn deploy(&mut self) -> Result<()> { // First, we need to deploy the new version of the lambda - let lambda_version = self.upload_lambda("releases").await.unwrap(); + let lambda_version = self.upload_lambda("releases").await?; // Next, we need to create a new deployment, pointing at our // new lambda version with canary settings - self.create_apig_deployment("Releases", "prod", "releases", &lambda_version, 10.0) - .await - .unwrap(); + self.create_apig_deployment("Releases", "prod", "releases", &lambda_version, 0.0) + .await?; Ok(()) }