mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-04 02:33:56 +00:00
Hold Daytona credentials as the SDK's configuration
DaytonaCredentials mirrored DaytonaConfig field for field and was copied into one at connect time. It is now a newtype over the SDK configuration with the API key always present and a Debug that never prints it; the driver's Daytona provider connects with the configuration as it is. Callers build it from an API key, a settings lookup, and the optional control-plane URL, organization, and HTTP client. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
91d905813f
commit
054a37f830
7 changed files with 80 additions and 92 deletions
|
|
@ -1005,13 +1005,12 @@ async fn check_install_daytona_api_key(
|
|||
state: &InstallAppState,
|
||||
api_key: String,
|
||||
) -> anyhow::Result<daytona::DaytonaKeyCheck> {
|
||||
let credentials = DaytonaCredentials {
|
||||
api_key,
|
||||
api_url: state.upstreams.daytona_api_base_url.clone(),
|
||||
organization_id: state.upstreams.daytona_organization_id.clone(),
|
||||
target: None,
|
||||
http_client: Some(fabro_http::http_client().context("failed to build HTTP client")?),
|
||||
};
|
||||
let credentials = DaytonaCredentials::new(api_key)
|
||||
.with_api_url(state.upstreams.daytona_api_base_url.clone())
|
||||
.with_organization_id(state.upstreams.daytona_organization_id.clone())
|
||||
.with_http_client(Some(
|
||||
fabro_http::http_client().context("failed to build HTTP client")?,
|
||||
));
|
||||
daytona::check_daytona_api_key(&credentials, daytona::DAYTONA_CREDENTIAL_PROBE_TIMEOUT).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1470,15 +1470,8 @@ impl AppState {
|
|||
/// the server's HTTP client. The process environment is consulted only
|
||||
/// through the configured lookup.
|
||||
pub(crate) fn daytona_credentials(&self, api_key: String) -> DaytonaCredentials {
|
||||
DaytonaCredentials {
|
||||
api_key,
|
||||
api_url: self
|
||||
.config_env_lookup(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|| self.config_env_lookup(EnvVars::DAYTONA_SERVER_URL)),
|
||||
organization_id: self.config_env_lookup(EnvVars::DAYTONA_ORGANIZATION_ID),
|
||||
target: None,
|
||||
http_client: self.http_client().ok(),
|
||||
}
|
||||
DaytonaCredentials::from_api_key(api_key, |name| self.config_env_lookup(name))
|
||||
.with_http_client(self.http_client().ok())
|
||||
}
|
||||
|
||||
/// Everything a reconnect needs to reach a run's provider: the server's
|
||||
|
|
@ -2358,14 +2351,8 @@ fn build_sandbox_inventory(
|
|||
|
||||
if let Some(daytona) = provider_settings.get(&SandboxProviderKind::DAYTONA) {
|
||||
if let Some(api_key) = daytona_api_key.filter(|_| daytona.enabled) {
|
||||
let credentials = DaytonaCredentials {
|
||||
api_key,
|
||||
api_url: env_lookup(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|| env_lookup(EnvVars::DAYTONA_SERVER_URL)),
|
||||
organization_id: env_lookup(EnvVars::DAYTONA_ORGANIZATION_ID),
|
||||
target: None,
|
||||
http_client,
|
||||
};
|
||||
let credentials = DaytonaCredentials::from_api_key(api_key, |name| env_lookup(name))
|
||||
.with_http_client(http_client);
|
||||
inventory = inventory.with_lazy(
|
||||
SandboxProviderKind::DAYTONA,
|
||||
daytona.clone(),
|
||||
|
|
|
|||
|
|
@ -689,14 +689,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn credential_probe_reports_configured_timeout() {
|
||||
let credentials = DaytonaCredentials {
|
||||
api_key: "dtn_test".to_string(),
|
||||
// A non-routable address: the probe cannot finish within the budget.
|
||||
api_url: Some("http://10.255.255.1:1/api".to_string()),
|
||||
organization_id: None,
|
||||
target: None,
|
||||
http_client: None,
|
||||
};
|
||||
// A non-routable address: the probe cannot finish within the budget.
|
||||
let credentials = DaytonaCredentials::new("dtn_test".to_string())
|
||||
.with_api_url(Some("http://10.255.255.1:1/api".to_string()));
|
||||
let err = check_daytona_api_key(&credentials, Duration::from_millis(1))
|
||||
.await
|
||||
.expect_err("probe should time out");
|
||||
|
|
@ -737,15 +732,9 @@ mod wire_gate {
|
|||
)]
|
||||
fn live_credentials() -> Option<DaytonaCredentials> {
|
||||
let api_key = std::env::var(EnvVars::DAYTONA_API_KEY).ok()?;
|
||||
Some(DaytonaCredentials {
|
||||
api_key,
|
||||
api_url: std::env::var(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|_| std::env::var(EnvVars::DAYTONA_SERVER_URL))
|
||||
.ok(),
|
||||
organization_id: std::env::var(EnvVars::DAYTONA_ORGANIZATION_ID).ok(),
|
||||
target: None,
|
||||
http_client: None,
|
||||
})
|
||||
Some(DaytonaCredentials::from_api_key(api_key, |name| {
|
||||
std::env::var(name).ok()
|
||||
}))
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
|
|
|||
|
|
@ -38,39 +38,74 @@ pub const PLUGIN_BINARY_PREFIX: &str = "fabro-sandbox";
|
|||
/// `User-Agent` fabro presents to remote sandbox control planes.
|
||||
pub const USER_AGENT: &str = concat!("fabro-sandbox/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
/// Explicit Daytona credentials. The process environment is never consulted.
|
||||
/// Explicit Daytona credentials: the SDK's configuration with the API key
|
||||
/// always present and a `Debug` that never prints it. The process
|
||||
/// environment is never consulted.
|
||||
#[derive(Clone)]
|
||||
pub struct DaytonaCredentials {
|
||||
pub api_key: String,
|
||||
pub api_url: Option<String>,
|
||||
pub organization_id: Option<String>,
|
||||
pub target: Option<String>,
|
||||
/// Shared HTTP client; tests pass a no-proxy client here.
|
||||
pub http_client: Option<reqwest::Client>,
|
||||
}
|
||||
pub struct DaytonaCredentials(DaytonaConfig);
|
||||
|
||||
impl DaytonaCredentials {
|
||||
/// Credentials for `api_key` against Daytona's public control plane,
|
||||
/// presenting fabro's `User-Agent`.
|
||||
#[must_use]
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self(DaytonaConfig {
|
||||
api_key: Some(api_key),
|
||||
user_agent: Some(USER_AGENT.to_string()),
|
||||
..DaytonaConfig::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Credentials for a vault API key, with the control-plane URL and
|
||||
/// organization taken from `lookup` (server configuration, or the
|
||||
/// process environment in a CLI worker). Nothing is read implicitly.
|
||||
pub fn from_api_key(api_key: String, lookup: impl Fn(&str) -> Option<String>) -> Self {
|
||||
Self {
|
||||
api_key,
|
||||
api_url: lookup(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|| lookup(EnvVars::DAYTONA_SERVER_URL)),
|
||||
organization_id: lookup(EnvVars::DAYTONA_ORGANIZATION_ID),
|
||||
target: None,
|
||||
http_client: None,
|
||||
}
|
||||
Self::new(api_key)
|
||||
.with_api_url(
|
||||
lookup(EnvVars::DAYTONA_API_URL).or_else(|| lookup(EnvVars::DAYTONA_SERVER_URL)),
|
||||
)
|
||||
.with_organization_id(lookup(EnvVars::DAYTONA_ORGANIZATION_ID))
|
||||
}
|
||||
|
||||
/// The control-plane URL; Daytona's public API when `None`.
|
||||
#[must_use]
|
||||
pub fn with_api_url(mut self, api_url: Option<String>) -> Self {
|
||||
self.0.api_url = api_url;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_organization_id(mut self, organization_id: Option<String>) -> Self {
|
||||
self.0.organization_id = organization_id;
|
||||
self
|
||||
}
|
||||
|
||||
/// A shared HTTP client; tests pass a no-proxy client here.
|
||||
#[must_use]
|
||||
pub fn with_http_client(mut self, http_client: Option<reqwest::Client>) -> Self {
|
||||
self.0.http_client = http_client;
|
||||
self
|
||||
}
|
||||
|
||||
/// The API key, which every constructor sets.
|
||||
#[must_use]
|
||||
pub fn api_key(&self) -> &str {
|
||||
self.0.api_key.as_deref().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The SDK configuration the driver's Daytona provider connects with.
|
||||
#[must_use]
|
||||
pub fn config(&self) -> &DaytonaConfig {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DaytonaCredentials {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DaytonaCredentials")
|
||||
.field("api_url", &self.api_url)
|
||||
.field("organization_id", &self.organization_id)
|
||||
.field("target", &self.target)
|
||||
.field("api_url", &self.0.api_url)
|
||||
.field("organization_id", &self.0.organization_id)
|
||||
.field("target", &self.0.target)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
|
@ -187,17 +222,8 @@ pub async fn connect_provider(
|
|||
.daytona
|
||||
.as_ref()
|
||||
.ok_or(ConnectError::MissingDaytonaCredentials)?;
|
||||
let config = DaytonaConfig {
|
||||
api_key: Some(credentials.api_key.clone()),
|
||||
jwt_token: None,
|
||||
organization_id: credentials.organization_id.clone(),
|
||||
api_url: credentials.api_url.clone(),
|
||||
target: credentials.target.clone(),
|
||||
http_client: credentials.http_client.clone(),
|
||||
user_agent: Some(USER_AGENT.to_string()),
|
||||
};
|
||||
Arc::new(
|
||||
DaytonaProvider::connect_explicit(config)
|
||||
DaytonaProvider::connect_explicit(credentials.config().clone())
|
||||
.await
|
||||
.map_err(driver)?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ pub async fn provider_sandbox(
|
|||
.ok_or_else(|| crate::Error::message(MISSING_DAYTONA_CREDENTIALS))?;
|
||||
let plan = daytona::create_plan(
|
||||
Arc::clone(&provider),
|
||||
credentials.api_key.clone(),
|
||||
credentials.api_key().to_string(),
|
||||
spec,
|
||||
run_id,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -533,16 +533,11 @@ mod daytona_streaming_live {
|
|||
reason = "live smoke tests take Daytona credentials from the developer's environment"
|
||||
)]
|
||||
fn live_credentials() -> Result<DaytonaCredentials> {
|
||||
Ok(DaytonaCredentials {
|
||||
api_key: std::env::var(EnvVars::DAYTONA_API_KEY)
|
||||
.context("DAYTONA_API_KEY must be set")?,
|
||||
api_url: std::env::var(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|_| std::env::var(EnvVars::DAYTONA_SERVER_URL))
|
||||
.ok(),
|
||||
organization_id: std::env::var(EnvVars::DAYTONA_ORGANIZATION_ID).ok(),
|
||||
target: None,
|
||||
http_client: None,
|
||||
})
|
||||
let api_key =
|
||||
std::env::var(EnvVars::DAYTONA_API_KEY).context("DAYTONA_API_KEY must be set")?;
|
||||
Ok(DaytonaCredentials::from_api_key(api_key, |name| {
|
||||
std::env::var(name).ok()
|
||||
}))
|
||||
}
|
||||
|
||||
fn daytona_access(credentials: DaytonaCredentials) -> ProviderAccess {
|
||||
|
|
|
|||
|
|
@ -194,16 +194,8 @@ fn daytona_access(credentials: DaytonaCredentials) -> ProviderAccess {
|
|||
}
|
||||
|
||||
fn live_daytona_credentials() -> DaytonaCredentials {
|
||||
DaytonaCredentials {
|
||||
api_key: std::env::var(EnvVars::DAYTONA_API_KEY)
|
||||
.expect("DAYTONA_API_KEY must be set"),
|
||||
api_url: std::env::var(EnvVars::DAYTONA_API_URL)
|
||||
.or_else(|_| std::env::var(EnvVars::DAYTONA_SERVER_URL))
|
||||
.ok(),
|
||||
organization_id: std::env::var(EnvVars::DAYTONA_ORGANIZATION_ID).ok(),
|
||||
target: None,
|
||||
http_client: None,
|
||||
}
|
||||
let api_key = std::env::var(EnvVars::DAYTONA_API_KEY).expect("DAYTONA_API_KEY must be set");
|
||||
DaytonaCredentials::from_api_key(api_key, |name| std::env::var(name).ok())
|
||||
}
|
||||
|
||||
async fn create_env() -> RunSandbox {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue