1use eggup_core::{
10 AbsentPolicy, AllValidators, ArtifactMember, ArtifactSet, CommitOwnership,
11 ExistingAsOwnedVerifier, InstallPlan, IntegrityRequirement, MemberId, Ownership,
12 OwnershipVerifier, ProductId, ReleaseId, TransactionDisposition,
13};
14use std::fmt::Debug;
15use std::fs;
16use std::path::{Path, PathBuf};
17use std::time::{SystemTime, UNIX_EPOCH};
18
19#[derive(Debug)]
20struct DenyAll;
21impl OwnershipVerifier for DenyAll {
22 fn verify(&self, _member: &MemberId, _destination: &Path) -> Ownership {
23 Ownership::Foreign
24 }
25}
26
27fn temp_root(prefix: &str) -> PathBuf {
28 let nanos = SystemTime::now()
29 .duration_since(UNIX_EPOCH)
30 .map(|d| d.as_nanos())
31 .unwrap_or(0);
32 std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id()))
33}
34
35fn build(
36 new: &[u8],
37 old: Option<&[u8]>,
38) -> Result<(PathBuf, InstallPlan), Box<dyn std::error::Error>> {
39 let base = temp_root("eggup-receipts");
40 let inputs = base.join("inputs");
41 let root = base.join("install");
42 fs::create_dir_all(&inputs)?;
43 fs::create_dir_all(root.join("bin"))?;
44 fs::write(inputs.join("app"), new)?;
45 if let Some(old) = old {
46 fs::write(root.join("bin/app"), old)?;
47 }
48 let digest = eggup_core::hash_file(&inputs.join("app"))?;
49 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
50 .with_integrity(IntegrityRequirement::Sha256(digest));
51 let plan = InstallPlan::new(
52 ProductId::new("demo")?,
53 ReleaseId::new("5.0.0")?,
54 &root,
55 ArtifactSet::single(member)?,
56 )?;
57 Ok((base, plan))
59}
60
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 let (base, plan) = build(b"new", Some(b"old"))?;
64 let receipt = plan
65 .prepare()?
66 .verify_integrity()?
67 .validate(&AllValidators::new())?
68 .commit(CommitOwnership::new(
69 &ExistingAsOwnedVerifier,
70 AbsentPolicy::AllowCreate,
71 ))?;
72 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73 assert!(receipt.failure().is_none());
74 println!("committed: {:?}", receipt.disposition());
75 let _ = fs::remove_dir_all(&base);
76
77 let (base, plan) = build(b"new", Some(b"old"))?;
79 let root = base.join("install");
80 let receipt = plan
81 .prepare()?
82 .verify_integrity()?
83 .validate(&AllValidators::new())?
84 .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85 assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86 assert!(receipt.rollback_verified());
87 let failure = receipt.failure().expect("rolled back carries a cause");
88 println!(
89 "rolled back: phase={} category={} detail={}",
90 failure.phase().as_str(),
91 failure.category().as_str(),
92 failure.detail()
93 );
94 assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95 assert_ne!(
96 receipt.disposition(),
97 TransactionDisposition::Committed,
98 "rollback is not success"
99 );
100 let _ = fs::remove_dir_all(&base);
101
102 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107 Ok(())
108}