use crate::credentials;
use crate::project_config::ProjectConfig;
pub struct CliConfig {
pub api_url: String,
pub tenant_slug: String,
pub api_key: Option<String>,
pub env_name: Option<String>,
pub keycloak_url: Option<String>,
pub keycloak_realm: Option<String>,
pub keycloak_client_id: Option<String>,
pub tenant_override: Option<String>,
}
impl CliConfig {
pub fn load_with_env(env: Option<&str>) -> eyre::Result<Self> {
let project = ProjectConfig::find_and_load()?;
if let Some(ref project) = project {
if let Some(env_config) = project.resolve_env(env)? {
let env_name = env
.map(|s| s.to_string())
.or_else(|| project.service.default_env.clone());
let api_key = env_config
.api_key
.clone()
.or_else(|| {
env_config
.api_key_env
.as_ref()
.and_then(|var| std::env::var(var).ok())
})
.or_else(|| std::env::var("CUFFLINK_API_KEY").ok())
.or_else(|| {
credentials::load_for(&env_config.api_url, &env_config.tenant)
.ok()
.flatten()
.map(|c| c.api_key)
});
return Ok(Self {
api_url: env_config.api_url.clone(),
tenant_slug: env_config.tenant.clone(),
api_key,
env_name,
keycloak_url: env_config
.keycloak_url
.clone()
.or_else(|| std::env::var("CUFFLINK_KEYCLOAK_URL").ok()),
keycloak_realm: env_config
.keycloak_realm
.clone()
.or_else(|| std::env::var("CUFFLINK_KEYCLOAK_REALM").ok()),
keycloak_client_id: env_config
.keycloak_client_id
.clone()
.or_else(|| std::env::var("CUFFLINK_CLIENT_ID").ok()),
tenant_override: None,
});
}
}
eyre::bail!(
"No Cufflink.toml found. Run from a cufflink project directory or use `cufflink init` to create one."
)
}
pub fn for_platform(api_url_override: Option<&str>, env: Option<&str>) -> eyre::Result<Self> {
if let Ok(Some(project)) = ProjectConfig::find_and_load() {
if let Ok(Some(env_config)) = project.resolve_env(env) {
let api_url = api_url_override
.map(|s| s.to_string())
.unwrap_or_else(|| env_config.api_url.clone());
let env_name = env
.map(|s| s.to_string())
.or_else(|| project.service.default_env.clone());
return Ok(Self {
api_url,
tenant_slug: env_config.tenant.clone(),
api_key: None,
env_name,
keycloak_url: env_config
.keycloak_url
.clone()
.or_else(|| std::env::var("CUFFLINK_KEYCLOAK_URL").ok()),
keycloak_realm: env_config
.keycloak_realm
.clone()
.or_else(|| std::env::var("CUFFLINK_KEYCLOAK_REALM").ok()),
keycloak_client_id: env_config
.keycloak_client_id
.clone()
.or_else(|| std::env::var("CUFFLINK_CLIENT_ID").ok()),
tenant_override: None,
});
}
}
let api_url = api_url_override
.map(|s| s.to_string())
.or_else(|| std::env::var("CUFFLINK_API_URL").ok())
.unwrap_or_else(|| "http://localhost:8080".to_string());
Ok(Self {
api_url,
tenant_slug: String::new(),
api_key: None,
env_name: None,
keycloak_url: std::env::var("CUFFLINK_KEYCLOAK_URL").ok(),
keycloak_realm: std::env::var("CUFFLINK_KEYCLOAK_REALM").ok(),
keycloak_client_id: std::env::var("CUFFLINK_CLIENT_ID").ok(),
tenant_override: None,
})
}
pub fn http_client(&self) -> reqwest::Client {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(120))
.build()
.expect("Failed to build HTTP client")
}
pub async fn find_service_id(&self, service_name: &str) -> eyre::Result<String> {
let client = self.http_client();
let resp = self
.auth_request(
&client,
reqwest::Method::GET,
&format!("{}/api/services", self.api_url),
)
.send()
.await?;
let body: serde_json::Value = resp.json().await?;
body["services"]
.as_array()
.and_then(|arr| {
arr.iter()
.find(|s| s["name"].as_str() == Some(service_name))
.and_then(|s| s["id"].as_str())
})
.map(String::from)
.ok_or_else(|| eyre::eyre!("Service '{}' not found on platform", service_name))
}
pub fn auth_request(
&self,
client: &reqwest::Client,
method: reqwest::Method,
url: &str,
) -> reqwest::RequestBuilder {
if let Some(ref tenant) = self.tenant_override {
if let Ok(platform_key) = std::env::var("CUFFLINK_PLATFORM_API_KEY") {
return client
.request(method, url)
.header("Authorization", format!("ApiKey {}", platform_key))
.header("X-Tenant-Slug", tenant.as_str());
}
}
let key = match &self.api_key {
Some(k) => k,
None => {
eprintln!(
"Error: No credentials found for tenant '{}'.",
self.tenant_slug
);
eprintln!();
eprintln!(" Run `cufflink login` to authenticate with your tenant.");
eprintln!(" Or set CUFFLINK_API_KEY in your environment.");
std::process::exit(1);
}
};
client
.request(method, url)
.header("Authorization", format!("ApiKey {}", key))
}
}