Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/onboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ use yq instead of jq for local json/yaml scripting

codebuild projects are in infra/terraform/aws/modules/ci/v001. per-service builds in main.tf (sources codebuild module), integ tests in integ.tf (buildspec inlined). ci module is only used by init-dev

buildspecs use runtime config via env vars (RUN_TESTS, PUSH_IMAGE, DEPLOY_LAMBDA, DEPLOY_ECS) set by `--environment-variables-override` in ecr-images.sh
buildspecs use runtime config via env vars (RUN_TESTS, PUSH_IMAGE, DEPLOY, DEPLOY_TARGET). DEPLOY_TARGET is set per codebuild project in terraform. DEPLOY_LAMBDA/DEPLOY_ECS are derived in the buildspec from DEPLOY + DEPLOY_TARGET

s3 upload to artifacts bucket auto-triggers codepipeline via eventbridge when using `--build --push` without `--service`
`--build` without `--service` triggers codepipeline via `start-pipeline-execution` with pipeline variables. `--build --service <name>` starts a direct codebuild job with `--environment-variables-override`

say "design" instead of "approach"
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resolver = "2"
members = [
"crates/cache",
"crates/cognitoidp",
"crates/envvar",
"crates/httpclient",
"crates/pg",
"crates/pubsub",
Expand Down
1 change: 1 addition & 0 deletions crates/cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ thiserror = "2.0"
serde_json = "1.0"
tracing = "0.1"
tokio = { version = "1.49", features = ["macros", "rt-multi-thread"] }
envvar = { path = "../envvar" }
types = { path = "../types" }

# redis
Expand Down
14 changes: 13 additions & 1 deletion crates/cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ pub enum CacheError {
DeserializationError(String),
}

impl From<String> for CacheError {
fn from(s: String) -> Self {
CacheError::ConnectionError(s)
}
}

#[async_trait]
pub trait Cache: Send + Sync {
// transaction item rules by state
Expand Down Expand Up @@ -123,7 +129,13 @@ pub async fn new() -> Option<Arc<dyn Cache>> {
}
}
} else if env::var("REDIS_HOST").is_ok() {
let client = RedisClient::new().await;
let client = match RedisClient::new().await {
Ok(c) => c,
Err(e) => {
tracing::warn!("redis client creation failed: {}", e);
return None;
}
};
if let Err(e) = client.init().await {
tracing::warn!("redis init failed: {}", e);
None
Expand Down
61 changes: 14 additions & 47 deletions crates/cache/src/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,20 @@ pub struct RedisClient {
}

impl RedisClient {
pub async fn new() -> Self {
let redis_uri = Self::redis_uri_from_env();
let redis_config = Config::from_url(&redis_uri).unwrap();
let redis_client = Builder::from_config(redis_config).build().unwrap();
Self {
pub async fn new() -> Result<Self, CacheError> {
let redis_uri = Self::redis_uri_from_env()?;
let redis_config =
Config::from_url(&redis_uri).map_err(|e| CacheError::ConnectionError(e.to_string()))?;
let redis_client = Builder::from_config(redis_config)
.build()
.map_err(|e| CacheError::ConnectionError(e.to_string()))?;
Ok(Self {
inner: redis_client,
}
})
}

fn redis_uri_from_env() -> String {
let redis_db = std::env::var("REDIS_DB").unwrap();
let redis_host = std::env::var("REDIS_HOST").unwrap();
let redis_port = std::env::var("REDIS_PORT").unwrap();
let redis_username = std::env::var("REDIS_USERNAME").unwrap();
let redis_password = std::env::var("REDIS_PASSWORD").unwrap();
redis_uri(
&redis_db,
&redis_host,
&redis_port,
&redis_username,
&redis_password,
)
fn redis_uri_from_env() -> Result<String, CacheError> {
Ok(envvar::redis_uri()?)
}

pub async fn init(&self) -> Result<(), Error> {
Expand Down Expand Up @@ -111,16 +103,6 @@ impl RedisClient {
}
}

fn redis_uri(
redis_db: &str,
redis_host: &str,
redis_port: &str,
redis_username: &str,
redis_password: &str,
) -> String {
format!("redis://{redis_username}:{redis_password}@{redis_host}:{redis_port}/{redis_db}")
}

#[async_trait]
impl Cache for RedisClient {
async fn get_transaction_item_rules(
Expand Down Expand Up @@ -387,23 +369,6 @@ end
mod tests {
use super::*;

#[test]
fn it_creates_a_redis_conn_uri() {
let redis_db = "0";
let redis_host = "localhost";
let redis_port = "6379";
let redis_username = "admin";
let redis_password = "password";
let uri = redis_uri(
redis_db,
redis_host,
redis_port,
redis_username,
redis_password,
);
assert_eq!(uri, "redis://admin:password@localhost:6379/0");
}

#[test]
fn it_creates_rules_state_key() {
let key = CacheKey::rules_state(AccountRole::Creditor, "California").to_string();
Expand Down Expand Up @@ -448,7 +413,9 @@ mod integration_tests {
use types::rule::{ApprovalRuleInstance, TransactionItemRuleInstance};

async fn get_client() -> RedisClient {
let client = RedisClient::new().await;
let client = RedisClient::new()
.await
.expect("failed to create redis client");
client.init().await.expect("failed to init redis");
client
}
Expand Down
6 changes: 6 additions & 0 deletions crates/envvar/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "envvar"
version = "0.1.0"
edition = "2021"
rust-version.workspace = true
description = "shared env var reading"
33 changes: 33 additions & 0 deletions crates/envvar/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::env;

pub fn required(var: &str) -> Result<String, String> {
env::var(var).map_err(|e| format!("{var} not set: {e}"))
}

pub fn optional(var: &str, default: &str) -> String {
env::var(var).unwrap_or_else(|_| default.to_string())
}

pub fn redis_uri() -> Result<String, String> {
let db = required("REDIS_DB")?;
let host = required("REDIS_HOST")?;
let port = required("REDIS_PORT")?;
let username = required("REDIS_USERNAME")?;
let password = required("REDIS_PASSWORD")?;
Ok(format!("redis://{username}:{password}@{host}:{port}/{db}"))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_formats_error_with_var_name() {
let result = required("NONEXISTENT_TEST_VAR");
let err = result.unwrap_err();
assert_eq!(
err,
"NONEXISTENT_TEST_VAR not set: environment variable not found"
);
}
}
2 changes: 2 additions & 0 deletions crates/httpclient/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ aws-sigv4 = "1.3"
aws-smithy-runtime-api = "1.10"
http = "1.4"
reqwest = { version = "0.13", features = ["json"] }
serde_json = "1"
thiserror = "2.0"

[dev-dependencies]
httpmock = "0.8"
Expand Down
Loading
Loading