Reject invalid deployment configuration early

This commit is contained in:
ms2k 2026-07-19 02:01:37 +09:00
commit 0e351c3640

View file

@ -104,7 +104,7 @@ impl ApiApplicationConfig {
let lease_ttl = env_duration("REMORA_LEASE_TTL_SECONDS", 30)?;
let target = TargetStoreConfig::from_env().map_err(runtime_error)?;
let vault = VaultConfig::from_env().map_err(runtime_error)?;
Ok(Self {
let config = Self {
public_bind: env_parse("REMORA_PUBLIC_BIND", "0.0.0.0:8080")?,
worker_bind: env_parse("REMORA_WORKER_BIND", "0.0.0.0:8443")?,
management: ManagementConfig::from_env(crate::COMPONENT_NAME)?,
@ -156,10 +156,54 @@ impl ApiApplicationConfig {
private_key: PathBuf::from(required_env("REMORA_WORKER_TLS_KEY_FILE")?),
client_ca: PathBuf::from(required_env("REMORA_WORKER_TLS_CLIENT_CA_FILE")?),
},
})
};
validate_api_limits(
config.database_connections,
&config.coordination,
&config.attempt,
&config.recovery,
&config.public_api,
)?;
Ok(config)
}
}
fn validate_api_limits(
database_connections: u32,
coordination: &StoreConfig,
attempt: &AttemptConfig,
recovery: &RecoveryConfig,
public_api: &PublicApiConfig,
) -> Result<(), RuntimeError> {
if database_connections == 0
|| coordination.global_limit == 0
|| coordination.lease_ttl.is_zero()
|| coordination.reserve_ttl.is_zero()
|| coordination.progress_ttl.is_zero()
|| attempt.max_attempts == 0
|| recovery.retry_delay.is_zero()
|| recovery.cleanup_claim_ttl.is_zero()
|| recovery.batch_size == 0
|| public_api.cursor_secret.is_empty()
|| public_api.member_access_ttl.is_zero()
|| public_api.event_retention.is_zero()
|| public_api.event_poll_interval.is_zero()
|| public_api.event_heartbeat_interval.is_zero()
|| public_api.event_ticket_ttl.is_zero()
|| public_api.event_ticket_ttl > Duration::from_secs(60)
|| public_api.event_allowed_origins.is_empty()
|| public_api
.event_allowed_origins
.iter()
.any(|origin| origin.is_empty() || origin.trim() != origin)
{
return Err(RuntimeError::message(
"API limits and timing must be positive and event ticket expiry must not exceed 60 seconds",
));
}
Ok(())
}
impl ApiApplication {
#[must_use]
pub fn new(config: ApiApplicationConfig) -> Self {
@ -657,4 +701,33 @@ mod tests {
"cannot read REMORA_CURSOR_SECRET_FILE: entity not found"
);
}
#[test]
fn validates_api_limits_before_external_connections() {
let coordination = StoreConfig {
global_limit: 1,
lease_ttl: Duration::from_secs(30),
reserve_ttl: Duration::from_secs(30),
progress_ttl: Duration::from_secs(60),
cancel_ttl: None,
};
let attempt = AttemptConfig { max_attempts: 1 };
let recovery = RecoveryConfig {
retry_delay: Duration::from_secs(1),
cleanup_claim_ttl: Duration::from_secs(30),
batch_size: 1,
};
let mut public_api = PublicApiConfig {
cursor_secret: vec![1; 32],
member_access_ttl: Duration::from_secs(60),
event_retention: Duration::from_secs(3600),
event_poll_interval: Duration::from_millis(250),
event_heartbeat_interval: Duration::from_secs(15),
event_ticket_ttl: Duration::from_secs(30),
event_allowed_origins: vec!["https://remora.example".into()],
};
assert!(validate_api_limits(1, &coordination, &attempt, &recovery, &public_api).is_ok());
public_api.event_ticket_ttl = Duration::from_secs(61);
assert!(validate_api_limits(1, &coordination, &attempt, &recovery, &public_api).is_err());
}
}