use tracing::debug;
use tracing::info;
use crate::flow;
use crate::io::manifest::resolve_tag;
use crate::io::remote::Remote;
use crate::io::storage::Storage;
use crate::lineage::DomainLineage;
use crate::lineage::PackageLineage;
use crate::paths::copy_cached_to_installed;
use crate::paths::DomainPaths;
use crate::uri::ManifestUri;
use crate::uri::Tag;
use crate::Error;
use crate::Res;
pub async fn install_package(
lineage: DomainLineage,
paths: &DomainPaths,
storage: &(impl Storage + Sync),
remote: &impl Remote,
manifest_uri: &ManifestUri,
) -> Res<DomainLineage> {
info!("⏳ Installing package: {}", manifest_uri.display());
paths
.scaffold_for_installing(storage, &lineage.home, &manifest_uri.namespace)
.await?;
paths
.scaffold_for_caching(storage, &manifest_uri.bucket)
.await?;
if lineage.packages.contains_key(&manifest_uri.namespace) {
debug!("❌ Package already installed: {}", manifest_uri.namespace);
return Err(Error::PackageAlreadyInstalled(
manifest_uri.namespace.clone(),
));
}
debug!("⏳ Caching remote manifest");
flow::cache_remote_manifest(paths, storage, remote, manifest_uri).await?;
debug!("⏳ Creating installed copy of manifest");
let installed_manifest_path =
paths.installed_manifest(&manifest_uri.namespace, &manifest_uri.hash);
copy_cached_to_installed(paths, storage, manifest_uri).await?;
debug!(
"✔️ Manifest installed at: {}",
installed_manifest_path.display()
);
debug!("⏳ Resolving latest hash for this package handle");
let latest = resolve_tag(
remote,
&manifest_uri.origin,
&manifest_uri.into(),
Tag::Latest,
)
.await?;
debug!("✔️ Latest hash is {}", latest.hash);
let mut lineage = lineage;
lineage.packages.insert(
manifest_uri.namespace.clone(),
PackageLineage::from_remote(manifest_uri.clone(), latest.hash),
);
info!(
"✔️ Successfully installed package: {}",
manifest_uri.display()
);
Ok(lineage)
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::str::FromStr;
use crate::fixtures;
use crate::io::remote::mocks::MockRemote;
use crate::io::storage::mocks::MockStorage;
use crate::io::storage::LocalStorage;
use crate::lineage::Home;
use crate::uri::S3Uri;
#[test(tokio::test)]
async fn test_if_already_installed() -> Res {
let (home, _temp_dir) = Home::from_temp_dir()?;
let namespace = ("foo", "bar");
let lineage = DomainLineage {
packages: BTreeMap::from([(namespace.into(), PackageLineage::default())]),
home,
};
let result = install_package(
lineage,
&DomainPaths::default(),
&MockStorage::default(),
&MockRemote::default(),
&ManifestUri {
namespace: namespace.into(),
..ManifestUri::default()
},
)
.await;
assert_eq!(
result.unwrap_err().to_string(),
"The package foo/bar is already installed"
);
Ok(())
}
#[test(tokio::test)]
async fn test_installing() -> Res {
let (lineage, _temp_dir) = DomainLineage::from_temp_dir()?;
let manifest_uri = ManifestUri {
bucket: "a".to_string(),
hash: fixtures::manifest::JSONL_HASH.to_string(),
namespace: ("f", "b").into(),
origin: None,
};
let jsonl = std::fs::read(fixtures::manifest::jsonl()?)?;
let remote = MockRemote::default();
let remote_uri = S3Uri::from_str(&format!(
"s3://{}/.quilt/packages/{}",
manifest_uri.bucket, manifest_uri.hash
))?;
remote
.put_object(&manifest_uri.origin, &remote_uri, jsonl)
.await?;
let latest_uri = S3Uri::from_str(&format!(
"s3://{}/.quilt/named_packages/{}/latest",
manifest_uri.bucket, manifest_uri.namespace
))?;
remote
.put_object(
&manifest_uri.origin,
&latest_uri,
manifest_uri.hash.as_bytes().to_vec(),
)
.await?;
let storage = MockStorage::default();
let result = install_package(
lineage,
&DomainPaths::default(),
&storage,
&remote,
&manifest_uri,
)
.await?;
let installed_package = result.packages.get(&("f", "b").into()).unwrap();
let tracked = installed_package.remote.clone();
assert_eq!(
installed_package.latest_hash,
fixtures::manifest::JSONL_HASH.to_string()
);
assert_eq!(tracked, manifest_uri);
let installed_manifest_path = PathBuf::from(format!(
".quilt/installed/{}/{}",
tracked.namespace, tracked.hash
));
assert!(storage.exists(&installed_manifest_path).await);
let cached_manifest_path = PathBuf::from(format!(
".quilt/packages/{}/{}",
tracked.bucket, tracked.hash
));
assert!(storage.exists(&cached_manifest_path).await);
Ok(())
}
#[test(tokio::test)]
async fn test_installing_when_no_permissions() -> Res {
let manifest_uri = ManifestUri {
bucket: "a".to_string(),
hash: "h".to_string(),
namespace: ("f", "b").into(),
origin: None,
};
let jsonl = std::fs::read(fixtures::manifest::jsonl()?)?;
let remote = MockRemote::default();
let remote_uri = S3Uri::from_str(&format!(
"s3://{}/.quilt/packages/{}",
manifest_uri.bucket, manifest_uri.hash
))?;
remote
.put_object(&manifest_uri.origin, &remote_uri, jsonl)
.await?;
let (lineage, _temp_dir) = DomainLineage::from_temp_dir()?;
let storage = LocalStorage::new();
let result = install_package(
lineage,
&DomainPaths::new(PathBuf::from("/")),
&storage,
&remote,
&manifest_uri,
)
.await;
let err = result.unwrap_err();
if let Error::Io(orig_err) = err {
assert_eq!(orig_err.kind(), std::io::ErrorKind::PermissionDenied);
} else {
panic!("Expected IO error, got: {err:?}");
}
Ok(())
}
}