use std::path::Path;
use greentic_extension_sdk_registry::config::{GREENTIC_STORE_NAME, GREENTIC_STORE_URL};
use greentic_extension_sdk_registry::local::LocalFilesystemRegistry;
use greentic_extension_sdk_registry::oci::OciRegistry;
use greentic_extension_sdk_registry::registry::ExtensionRegistry;
use greentic_extension_sdk_registry::store::GreenticStoreRegistry;
pub(super) enum Backend {
Local(LocalFilesystemRegistry),
Store(GreenticStoreRegistry),
Oci(OciRegistry),
}
impl Backend {
pub(super) async fn publish(
&self,
req: greentic_extension_sdk_registry::publish::PublishRequest,
) -> Result<
greentic_extension_sdk_registry::publish::PublishReceipt,
greentic_extension_sdk_registry::RegistryError,
> {
match self {
Backend::Local(r) => r.publish(req).await,
Backend::Store(r) => r.publish(req).await,
Backend::Oci(r) => r.publish(req).await,
}
}
}
pub(super) fn resolve_backend(
uri: &str,
home: &Path,
oci_token_override: Option<&str>,
) -> anyhow::Result<Backend> {
if uri == "local" {
let root = home.join("registries/local");
return Ok(Backend::Local(LocalFilesystemRegistry::new(
"publish-local",
root,
)));
}
if let Some(rest) = uri.strip_prefix("file://") {
let root = std::path::PathBuf::from(rest);
return Ok(Backend::Local(LocalFilesystemRegistry::new("file", root)));
}
if let Some(rest) = uri.strip_prefix("oci://") {
return build_oci_backend(rest, oci_token_override);
}
let cfg = greentic_extension_sdk_registry::config::load(&home.join("config.toml"))
.map_err(|e| anyhow::anyhow!("load config: {e}"))?;
let (name, url, token_env) = match cfg.registries.iter().find(|e| e.name == uri) {
Some(entry) => (
entry.name.clone(),
entry.url.clone(),
entry.token_env.clone(),
),
None if uri == GREENTIC_STORE_NAME => (
GREENTIC_STORE_NAME.to_string(),
GREENTIC_STORE_URL.to_string(),
None,
),
None => {
return Err(anyhow::anyhow!(
"no registry named '{uri}' in {}/config.toml. Add one with: gtdx registries add {uri} <url>",
home.display()
));
}
};
let token = crate::commands::resolve_registry_token(home, &name, token_env.as_deref());
let allow_insecure = crate::registry_security::insecure_registry_opt_in();
Ok(Backend::Store(
GreenticStoreRegistry::new(&name, &url, token).with_insecure_allowed(allow_insecure),
))
}
fn build_oci_backend(spec: &str, oci_token_override: Option<&str>) -> anyhow::Result<Backend> {
let (host, rest) = spec.split_once('/').ok_or_else(|| {
anyhow::anyhow!(
"oci:// URI must include at least a namespace: oci://<host>/<namespace>[/<name>]"
)
})?;
if host.is_empty() {
anyhow::bail!("oci:// URI missing host: {spec}");
}
let (namespace, artifact_name) = match rest.rsplit_once('/') {
Some((ns, name)) if !ns.is_empty() && !name.is_empty() => {
(ns.to_string(), Some(name.to_string()))
}
_ => (rest.to_string(), None),
};
if namespace.is_empty() {
anyhow::bail!("oci:// URI namespace is empty: {spec}");
}
let token = oci_token_override
.map(str::to_string)
.or_else(|| non_empty_env("GHCR_TOKEN"))
.or_else(|| non_empty_env("GITHUB_TOKEN"))
.or_else(|| non_empty_env("OCI_TOKEN"));
let auth = token.map(|t| oci_basic_auth_for(host, t));
let mut reg = OciRegistry::new(format!("oci-{host}"), host, namespace, auth);
if let Some(name) = artifact_name {
reg = reg.with_artifact_name(name);
}
Ok(Backend::Oci(reg))
}
fn non_empty_env(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|s| !s.is_empty())
}
fn oci_basic_auth_for(host: &str, token: String) -> (String, String) {
let user = if host.ends_with("ghcr.io") {
"oauth2".to_string()
} else {
"token".to_string()
};
(user, token)
}