Skip to content

Commit 4b31fa9

Browse files
committed
worker/jobs/downloads: Extract CdnLogStorageConfig enum
This allows us to use in-memory storage for testing purposes and fall back to the local file system for development.
1 parent efd8fa6 commit 4b31fa9

File tree

5 files changed

+94
-12
lines changed

5 files changed

+94
-12
lines changed

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
mod balance_capacity;
22
mod base;
3+
mod cdn_log_storage;
34
mod database_pools;
45
mod sentry;
56
mod server;
67

78
pub use self::balance_capacity::BalanceCapacityConfig;
89
pub use self::base::Base;
10+
pub use self::cdn_log_storage::CdnLogStorageConfig;
911
pub use self::database_pools::{DatabasePools, DbPoolConfig};
1012
pub use self::sentry::SentryConfig;
1113
pub use self::server::Server;

src/config/cdn_log_storage.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use anyhow::Context;
2+
use crates_io_env_vars::{required_var, var};
3+
use secrecy::SecretString;
4+
use std::path::PathBuf;
5+
6+
#[derive(Debug, Clone)]
7+
pub enum CdnLogStorageConfig {
8+
S3 {
9+
access_key: String,
10+
secret_key: SecretString,
11+
},
12+
Local {
13+
path: PathBuf,
14+
},
15+
Memory,
16+
}
17+
18+
impl CdnLogStorageConfig {
19+
pub fn s3(access_key: String, secret_key: SecretString) -> Self {
20+
Self::S3 {
21+
access_key,
22+
secret_key,
23+
}
24+
}
25+
26+
pub fn local(path: PathBuf) -> Self {
27+
Self::Local { path }
28+
}
29+
30+
pub fn memory() -> Self {
31+
Self::Memory
32+
}
33+
34+
pub fn from_env() -> anyhow::Result<Self> {
35+
if let Some(access_key) = var("AWS_ACCESS_KEY")? {
36+
let secret_key = required_var("AWS_SECRET_KEY")?.into();
37+
return Ok(Self::s3(access_key, secret_key));
38+
}
39+
40+
let current_dir = std::env::current_dir();
41+
let current_dir = current_dir.context("Failed to read the current directory")?;
42+
43+
let path = current_dir.join("local_uploads");
44+
let path_display = path.display();
45+
if path.exists() {
46+
info!("Falling back to local CDN log storage at {path_display}");
47+
return Ok(Self::local(path));
48+
}
49+
50+
warn!("Falling back to in-memory CDN log storage because {path_display} does not exist");
51+
Ok(Self::memory())
52+
}
53+
}

src/config/server.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::Env;
88
use super::base::Base;
99
use super::database_pools::DatabasePools;
1010
use crate::config::balance_capacity::BalanceCapacityConfig;
11+
use crate::config::cdn_log_storage::CdnLogStorageConfig;
1112
use crate::middleware::cargo_compat::StatusCodeConfig;
1213
use crate::storage::StorageConfig;
1314
use crates_io_env_vars::{list, list_parsed, required_var, var, var_parsed};
@@ -34,6 +35,7 @@ pub struct Server {
3435
pub max_blocking_threads: Option<usize>,
3536
pub db: DatabasePools,
3637
pub storage: StorageConfig,
38+
pub cdn_log_storage: CdnLogStorageConfig,
3739
pub session_key: cookie::Key,
3840
pub gh_client_id: ClientId,
3941
pub gh_client_secret: ClientSecret,
@@ -172,6 +174,7 @@ impl Server {
172174
Ok(Server {
173175
db: DatabasePools::full_from_environment(&base)?,
174176
storage,
177+
cdn_log_storage: CdnLogStorageConfig::from_env()?,
175178
base,
176179
ip,
177180
port,

src/tests/util/test_app.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use super::{MockAnonymousUser, MockCookieUser, MockTokenUser};
22
use crate::util::chaosproxy::ChaosProxy;
33
use crate::util::github::{MockGitHubClient, MOCK_GITHUB_DATA};
44
use anyhow::Context;
5-
use crates_io::config::{self, BalanceCapacityConfig, Base, DatabasePools, DbPoolConfig};
5+
use crates_io::config::{
6+
self, BalanceCapacityConfig, Base, CdnLogStorageConfig, DatabasePools, DbPoolConfig,
7+
};
68
use crates_io::middleware::cargo_compat::StatusCodeConfig;
79
use crates_io::models::token::{CrateScope, EndpointScope};
810
use crates_io::rate_limiter::{LimitedAction, RateLimiterConfig};
@@ -422,6 +424,7 @@ fn simple_config() -> config::Server {
422424
max_blocking_threads: None,
423425
db,
424426
storage,
427+
cdn_log_storage: CdnLogStorageConfig::memory(),
425428
session_key: cookie::Key::derive_from("test this has to be over 32 bytes long".as_bytes()),
426429
gh_client_id: ClientId::new(dotenvy::var("GH_CLIENT_ID").unwrap_or_default()),
427430
gh_client_secret: ClientSecret::new(dotenvy::var("GH_CLIENT_SECRET").unwrap_or_default()),

src/worker/jobs/downloads.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use crate::config::CdnLogStorageConfig;
12
use crate::worker::Environment;
23
use anyhow::Context;
34
use crates_io_cdn_logs::{count_downloads, Decompressor};
4-
use crates_io_env_vars::required_var;
55
use crates_io_worker::BackgroundJob;
66
use object_store::aws::AmazonS3Builder;
7+
use object_store::local::LocalFileSystem;
8+
use object_store::memory::InMemory;
79
use object_store::ObjectStore;
810
use std::cmp::Reverse;
911
use std::collections::HashSet;
@@ -33,16 +35,9 @@ impl BackgroundJob for ProcessCdnLog {
3335

3436
type Context = Arc<Environment>;
3537

36-
async fn run(&self, _ctx: Self::Context) -> anyhow::Result<()> {
37-
let access_key = required_var("AWS_ACCESS_KEY")?;
38-
let secret_key = required_var("AWS_SECRET_KEY")?;
39-
40-
let store = AmazonS3Builder::new()
41-
.with_region(&self.region)
42-
.with_bucket_name(&self.bucket)
43-
.with_access_key_id(access_key)
44-
.with_secret_access_key(secret_key)
45-
.build()
38+
async fn run(&self, ctx: Self::Context) -> anyhow::Result<()> {
39+
let store = self
40+
.build_store(&ctx.config.cdn_log_storage)
4641
.context("Failed to build object store")?;
4742

4843
let path = object_store::path::Path::parse(&self.path)
@@ -104,3 +99,29 @@ impl BackgroundJob for ProcessCdnLog {
10499
Ok(())
105100
}
106101
}
102+
103+
impl ProcessCdnLog {
104+
fn build_store(&self, config: &CdnLogStorageConfig) -> anyhow::Result<Box<dyn ObjectStore>> {
105+
match config {
106+
CdnLogStorageConfig::S3 {
107+
access_key,
108+
secret_key,
109+
} => {
110+
use secrecy::ExposeSecret;
111+
112+
let store = AmazonS3Builder::new()
113+
.with_region(&self.region)
114+
.with_bucket_name(&self.bucket)
115+
.with_access_key_id(access_key)
116+
.with_secret_access_key(secret_key.expose_secret())
117+
.build()?;
118+
119+
Ok(Box::new(store))
120+
}
121+
CdnLogStorageConfig::Local { path } => {
122+
Ok(Box::new(LocalFileSystem::new_with_prefix(path)?))
123+
}
124+
CdnLogStorageConfig::Memory => Ok(Box::new(InMemory::new())),
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)