mod archive;
mod config;
mod digest;
mod http;
mod install;
mod layout;
mod manifest;
mod name;
mod tree;
mod version;
mod write;
#[cfg(test)]
mod tests;
use camino::{Utf8Path, Utf8PathBuf};
use color_eyre::eyre::Report;
use tracing::info;
pub use self::{
config::resolve_extension_cache_dir,
digest::{InvalidDigest, Sha256Hex},
http::{is_permitted_url, redact_url},
layout::{ALLOWED_PREFIXES, classify_entry_path},
manifest::{
ArtifactQuery,
Manifest,
ManifestArtifact,
ManifestExtension,
ManifestSource,
ManifestSourceInfo,
SUPPORTED_SCHEMA_VERSION,
Selection,
},
name::ExtensionName,
version::{parse_pg_config_version, running_version},
};
use crate::error::{BootstrapError, BootstrapErrorKind, BootstrapResult};
pub(crate) const LOG_TARGET: &str = "pg_embed::extensions";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionRequest {
pub names: Vec<ExtensionName>,
pub manifest: ManifestSource,
pub cache_dir: Utf8PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArchiveOrigin {
Cached,
Downloaded,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledExtension {
pub name: ExtensionName,
pub version: String,
pub postgresql: String,
pub target: String,
pub archive_sha256: Sha256Hex,
pub origin: ArchiveOrigin,
pub files: Vec<Utf8PathBuf>,
}
#[must_use]
pub const fn compile_target() -> &'static str { env!("PG_EMBED_TARGET") }
pub(crate) const fn extension_error(kind: BootstrapErrorKind, report: Report) -> BootstrapError {
BootstrapError::new(kind, report)
}
pub fn install_extensions(
request: &ExtensionRequest,
install_dir: &Utf8Path,
) -> BootstrapResult<Vec<InstalledExtension>> {
if tokio::runtime::Handle::try_current().is_ok() {
std::thread::scope(|scope| {
scope
.spawn(|| install_extensions_blocking(request, install_dir))
.join()
.unwrap_or_else(|payload| Err(install_thread_panic(payload)))
})
} else {
install_extensions_blocking(request, install_dir)
}
}
pub async fn install_extensions_async(
request: ExtensionRequest,
install_dir: Utf8PathBuf,
) -> BootstrapResult<Vec<InstalledExtension>> {
tokio::task::spawn_blocking(move || install_extensions_blocking(&request, &install_dir))
.await
.unwrap_or_else(|err| {
Err(extension_error(
BootstrapErrorKind::ExtensionInstallFailed,
Report::new(err).wrap_err("extension install task failed"),
))
})
}
fn install_thread_panic(payload: Box<dyn std::any::Any + Send>) -> BootstrapError {
let message = crate::cluster::panic_utils::panic_payload_to_string(payload);
extension_error(
BootstrapErrorKind::ExtensionInstallFailed,
Report::msg(format!("extension install thread panicked: {message}")),
)
}
fn install_extensions_blocking(
request: &ExtensionRequest,
install_dir: &Utf8Path,
) -> BootstrapResult<Vec<InstalledExtension>> {
let span = tracing::info_span!(
target: LOG_TARGET,
"install_extensions",
install_dir = %install_dir,
names = request.names.len()
);
let _entered = span.enter();
let manifest = manifest::load(&request.manifest)?;
let running = running_version(install_dir)?;
let selections = select_all(request, &manifest, &running)?;
request
.names
.iter()
.zip(selections)
.map(|(name, selection)| install_one(request, install_dir, name, selection))
.collect()
}
fn select_all<'a>(
request: &ExtensionRequest,
manifest: &'a Manifest,
running: &postgresql_embedded::Version,
) -> BootstrapResult<Vec<manifest::Selection<'a>>> {
request
.names
.iter()
.map(|name| {
let query = ArtifactQuery {
name,
running,
target: compile_target(),
};
manifest.select(query, &request.manifest)
})
.collect()
}
fn install_one(
request: &ExtensionRequest,
install_dir: &Utf8Path,
name: &ExtensionName,
selection: manifest::Selection<'_>,
) -> BootstrapResult<InstalledExtension> {
tracing::debug!(
target: LOG_TARGET,
name = %name,
version = %selection.extension.version,
postgresql = %selection.artifact.postgresql,
target = %selection.artifact.target,
file = %selection.artifact.file,
"selected extension artefact"
);
let acquired = archive::acquire(&request.cache_dir, selection.artifact)?;
let files = install::install_archive(&acquired.path, selection.artifact, install_dir)?;
let installed = InstalledExtension {
name: name.clone(),
version: selection.extension.version.clone(),
postgresql: selection.artifact.postgresql.clone(),
target: selection.artifact.target.clone(),
archive_sha256: selection.artifact.sha256.clone(),
origin: acquired.origin,
files,
};
log_installed(&installed);
Ok(installed)
}
fn log_installed(installed: &InstalledExtension) {
info!(
target: LOG_TARGET,
name = %installed.name,
version = %installed.version,
postgresql = %installed.postgresql,
target = %installed.target,
origin = ?installed.origin,
files = installed.files.len(),
"installed extension"
);
}