Skip to content

Commit

Permalink
Merge pull request #41 from wack/eric/multi-313-v2
Browse files Browse the repository at this point in the history
Add canary traffic changing
  • Loading branch information
RobbieMcKinstry authored Nov 26, 2024
2 parents c0f3e26 + 341dd29 commit b03e917
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
*.zip
71 changes: 66 additions & 5 deletions src/adapters/ingresses/apig.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::path::PathBuf;

use crate::utils::load_default_aws_config;
use crate::WholePercent;

use super::Ingress;
use async_trait::async_trait;
use aws_sdk_apigateway::types::{Op, PatchOperation, RestApi};
use miette::miette;
use miette::{IntoDiagnostic, Result};
use tokio::{fs::File, io::AsyncReadExt};
Expand Down Expand Up @@ -84,7 +86,7 @@ impl AwsApiGateway {
stage_name: &str,
lambda_name: &str,
lambda_version: &str,
traffic_percentage: f64,
traffic_percentage: WholePercent,
) -> Result<()> {
// Update the APIG with the new lambda version
self.apig_client
Expand All @@ -105,8 +107,7 @@ impl AwsApiGateway {
.stage_name(stage_name)
.canary_settings(
DeploymentCanarySettings::builder()
.percent_traffic(traffic_percentage)
.use_stage_cache(false)
.percent_traffic(traffic_percentage.into_inner() as f64)
.build(),
)
.send()
Expand All @@ -115,6 +116,53 @@ impl AwsApiGateway {

Ok(())
}

pub async fn get_api_id_by_name(&self, api_name: &str) -> Result<RestApi> {
// Given an API Gateway's name, return its auto-generated AWS ID
let all_apis = self
.apig_client
.get_rest_apis()
.send()
.await
.into_diagnostic()?;

let api = all_apis
.items()
.iter()
.find(|api| api.name.clone().unwrap() == api_name)
.ok_or(miette!(
"Could not find an API Gateway with the name: {}",
api_name
))?;

Ok(api.clone())
}

pub async fn update_canary_traffic(
&self,
api_name: &str,
stage_name: &str,
traffic_percentage: WholePercent,
) -> Result<()> {
let api = self.get_api_id_by_name(api_name).await?;

let patch_op = PatchOperation::builder()
.op(Op::Replace)
.path("/canarySettings/percentTraffic")
.value(traffic_percentage.to_string())
.build();

self.apig_client
.update_stage()
.rest_api_id(api.id.unwrap_or_default())
.stage_name(stage_name)
.patch_operations(patch_op)
.send()
.await
.into_diagnostic()?;

Ok(())
}
}

/// given a path to a file, load it as an array of bytes.
Expand All @@ -130,11 +178,24 @@ async fn read_file(artifact_path: PathBuf) -> Result<Vec<u8>> {
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?;
let lambda_version = self.upload_lambda(&self.lambda_name).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, 0.0)
self.create_apig_deployment(
&self.gateway_name,
&self.stage_name,
&self.lambda_name,
&lambda_version,
WholePercent::try_new(0).into_diagnostic()?,
)
.await?;

Ok(())
}

async fn set_canary_traffic(&mut self, percent: WholePercent) -> Result<()> {
self.update_canary_traffic(&self.gateway_name, &self.stage_name, percent)
.await?;

Ok(())
Expand Down
7 changes: 6 additions & 1 deletion src/adapters/ingresses/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use async_trait::async_trait;
use miette::Result;

use crate::WholePercent;
pub use apig::AwsApiGateway;

/// Ingresses are responsible for (1) controlling how much traffic the canary
Expand All @@ -14,7 +15,7 @@ pub trait Ingress {
// TODO: define the other methods on this type.
// async fn yank_canary(&mut self) -> Result<()>;
// async fn promote_canary(&mut self) -> Result<()>;
// async fn set_canary_traffic(&mut self, percent: u8);
async fn set_canary_traffic(&mut self, percent: WholePercent) -> Result<()>;
}

pub struct MockIngress;
Expand All @@ -24,6 +25,10 @@ impl Ingress for MockIngress {
async fn deploy(&mut self) -> Result<()> {
todo!()
}

async fn set_canary_traffic(&mut self, _percent: WholePercent) -> Result<()> {
todo!()
}
}

impl From<MockIngress> for BoxIngress {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use config::Flags;
pub use pipeline::Pipeline;
pub(crate) use pipeline::WholePercent;

/// An adapter connects to some observable resource (like `CloudWatch`) and
/// emits events, like failed and succeeded requests.
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::{
use bon::bon;
use miette::Result;

pub(crate) use percent::WholePercent;

/// An alias for the Response Code-based monitor.
pub type ResponseMonitor = Box<dyn Monitor<Item = CategoricalObservation<5, ResponseStatusCode>>>;

Expand Down
4 changes: 2 additions & 2 deletions src/pipeline/percent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ impl DecimalPercent {
validate(less_or_equal = 100),
derive(Debug, Display, Copy, Clone, PartialEq, Eq, TryFrom, Into)
)]
pub(super) struct WholePercent(u8);
pub(crate) struct WholePercent(u8);

impl WholePercent {
/// returns "the rest" of the whole. That is, `100 - this value`.
pub(super) fn inverse(self) -> Self {
pub(crate) fn inverse(self) -> Self {
let val = u8::from(self);
Self::try_from(100 - val).unwrap()
}
Expand Down

0 comments on commit b03e917

Please sign in to comment.