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
41 changes: 13 additions & 28 deletions rust/config/src/eso_manifests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,14 @@ pub struct ClusterSecretStoreOptions {
///
/// org + environment are baked into the URL because ESO's webhook only templates
/// `{{ .remoteRef.key }}` per-secret — so a store is scoped to one (org, env).
pub fn build_cluster_secret_store(
opts: &ClusterSecretStoreOptions,
) -> Result<Value, SmooaiConfigError> {
pub fn build_cluster_secret_store(opts: &ClusterSecretStoreOptions) -> Result<Value, SmooaiConfigError> {
if opts.api_url.is_empty() {
return Err(SmooaiConfigError::new(
"build_cluster_secret_store: api_url is required",
));
}
if opts.org_id.is_empty() {
return Err(SmooaiConfigError::new(
"build_cluster_secret_store: org_id is required",
));
return Err(SmooaiConfigError::new("build_cluster_secret_store: org_id is required"));
}
if opts.environment.is_empty() {
return Err(SmooaiConfigError::new(
Expand Down Expand Up @@ -153,14 +149,9 @@ impl SecretMapping {
/// Returns `(config_key, env_var)`.
pub fn resolve_secret_mapping(m: &SecretMapping) -> Result<(String, String), SmooaiConfigError> {
if m.config_key.is_empty() {
return Err(SmooaiConfigError::new(
"resolve_secret_mapping: config_key is required",
));
return Err(SmooaiConfigError::new("resolve_secret_mapping: config_key is required"));
}
let env_var = m
.env_var
.clone()
.unwrap_or_else(|| camel_to_upper_snake(&m.config_key));
let env_var = m.env_var.clone().unwrap_or_else(|| camel_to_upper_snake(&m.config_key));
Ok((m.config_key.clone(), env_var))
}

Expand All @@ -181,14 +172,10 @@ pub struct ExternalSecretOptions {
/// @smooai/config key).
pub fn build_external_secret(opts: &ExternalSecretOptions) -> Result<Value, SmooaiConfigError> {
if opts.name.is_empty() {
return Err(SmooaiConfigError::new(
"build_external_secret: name is required",
));
return Err(SmooaiConfigError::new("build_external_secret: name is required"));
}
if opts.namespace.is_empty() {
return Err(SmooaiConfigError::new(
"build_external_secret: namespace is required",
));
return Err(SmooaiConfigError::new("build_external_secret: namespace is required"));
}
if opts.secrets.is_empty() {
return Err(SmooaiConfigError::new(
Expand All @@ -208,10 +195,7 @@ pub fn build_external_secret(opts: &ExternalSecretOptions) -> Result<Value, Smoo
data.push(json!({ "secretKey": env_var, "remoteRef": { "key": config_key } }));
}

let target_name = opts
.target_secret_name
.clone()
.unwrap_or_else(|| opts.name.clone());
let target_name = opts.target_secret_name.clone().unwrap_or_else(|| opts.name.clone());
let store_name = opts
.cluster_secret_store_name
.clone()
Expand Down Expand Up @@ -246,9 +230,7 @@ fn encode_query_component(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for b in s.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(b as char)
}
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => out.push(b as char),
b' ' => out.push_str("%20"),
_ => {
out.push('%');
Expand Down Expand Up @@ -311,8 +293,11 @@ mod tests {
fn resolve_mapping_defaults_and_override() {
let (_, env) = resolve_secret_mapping(&SecretMapping::new("mimoApiKey")).unwrap();
assert_eq!(env, "MIMO_API_KEY");
let (_, env2) =
resolve_secret_mapping(&SecretMapping::with_env_var("alibabaModelStudioApiKey", "DASHSCOPE_API_KEY")).unwrap();
let (_, env2) = resolve_secret_mapping(&SecretMapping::with_env_var(
"alibabaModelStudioApiKey",
"DASHSCOPE_API_KEY",
))
.unwrap();
assert_eq!(env2, "DASHSCOPE_API_KEY");
}

Expand Down
33 changes: 24 additions & 9 deletions rust/config/src/eso_refresher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ pub const ESO_REFRESHER_DEFAULT_INTERVAL_SECONDS: u64 = 900;
/// Writes the freshly-minted bearer token into the target Secret. Abstracted so
/// the refresh loop is unit-testable without a live cluster.
pub trait SecretWriter {
fn patch_bearer_token(
&self,
token: &str,
) -> impl Future<Output = Result<(), SmooaiConfigError>>;
fn patch_bearer_token(&self, token: &str) -> impl Future<Output = Result<(), SmooaiConfigError>>;
}

/// The slice of `TokenProvider` the refresher needs. The real `TokenProvider`
Expand Down Expand Up @@ -160,31 +157,49 @@ mod tests {

#[tokio::test]
async fn refresh_once_writes_fresh_token() {
let r = EsoRefresher::new(FakeTokenSource::new(&["tok-1"]), RecordingWriter::new(0), Duration::ZERO);
let r = EsoRefresher::new(
FakeTokenSource::new(&["tok-1"]),
RecordingWriter::new(0),
Duration::ZERO,
);
r.refresh_once().await.unwrap();
assert_eq!(*r.token_source.invalidations.borrow(), 1);
assert_eq!(r.secret_writer.written.borrow().clone(), vec!["tok-1".to_string()]);
}

#[tokio::test]
async fn forces_fresh_each_cycle() {
let r = EsoRefresher::new(FakeTokenSource::new(&["tok-1", "tok-2"]), RecordingWriter::new(0), Duration::ZERO);
let r = EsoRefresher::new(
FakeTokenSource::new(&["tok-1", "tok-2"]),
RecordingWriter::new(0),
Duration::ZERO,
);
r.refresh_once().await.unwrap();
r.refresh_once().await.unwrap();
assert_eq!(*r.token_source.calls.borrow(), 2);
assert_eq!(*r.token_source.invalidations.borrow(), 2);
assert_eq!(r.secret_writer.written.borrow().clone(), vec!["tok-1".to_string(), "tok-2".to_string()]);
assert_eq!(
r.secret_writer.written.borrow().clone(),
vec!["tok-1".to_string(), "tok-2".to_string()]
);
}

#[tokio::test]
async fn refresh_once_propagates_write_failure() {
let r = EsoRefresher::new(FakeTokenSource::new(&["tok-1"]), RecordingWriter::new(1), Duration::ZERO);
let r = EsoRefresher::new(
FakeTokenSource::new(&["tok-1"]),
RecordingWriter::new(1),
Duration::ZERO,
);
assert!(r.refresh_once().await.is_err());
}

#[tokio::test]
async fn defaults_interval_when_zero() {
let r = EsoRefresher::new(FakeTokenSource::new(&["t"]), RecordingWriter::new(0), Duration::ZERO);
assert_eq!(r.interval(), Duration::from_secs(ESO_REFRESHER_DEFAULT_INTERVAL_SECONDS));
assert_eq!(
r.interval(),
Duration::from_secs(ESO_REFRESHER_DEFAULT_INTERVAL_SECONDS)
);
}
}
Loading