pub struct InstallPlan { /* private fields */ }Expand description
An explicit installation root and a coherent artifact set ready for private preparation.
Implementations§
Source§impl InstallPlan
impl InstallPlan
Sourcepub fn new(
product: ProductId,
release: ReleaseId,
installation_root: impl Into<PathBuf>,
artifacts: ArtifactSet,
) -> Result<Self>
pub fn new( product: ProductId, release: ReleaseId, installation_root: impl Into<PathBuf>, artifacts: ArtifactSet, ) -> Result<Self>
Validates an installation root, destination containment, sources, and member uniqueness.
Examples found in repository?
examples/receipts.rs (lines 51-56)
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 // Leak the base via the plan's root; caller cleans `root.parent()`.
58 Ok((base, plan))
59}More examples
examples/one_member.rs (lines 35-40)
25fn main() -> Result<(), Box<dyn std::error::Error>> {
26 let base = temp_root("eggup-one-member");
27 let inputs = base.join("inputs");
28 let root = base.join("install");
29 fs::create_dir_all(inputs.join("bin"))?;
30 fs::create_dir_all(root.join("bin"))?;
31 fs::write(inputs.join("app"), b"new-app")?;
32 let digest = eggup_core::hash_file(&inputs.join("app"))?;
33 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
34 .with_integrity(IntegrityRequirement::Sha256(digest));
35 let plan = InstallPlan::new(
36 ProductId::new("demo")?,
37 ReleaseId::new("1.0.0")?,
38 &root,
39 ArtifactSet::single(member)?,
40 )?;
41 let receipt = plan
42 .prepare()?
43 .verify_integrity()?
44 .validate(&AllValidators::new())?
45 .commit(CommitOwnership::new(
46 &ExistingAsOwnedVerifier,
47 AbsentPolicy::AllowCreate,
48 ))?;
49 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
50 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
51 let _ = fs::remove_dir_all(&base);
52 println!("one-member commit: {:?}", receipt.disposition());
53 Ok(())
54}examples/custom_validator.rs (lines 56-61)
46fn main() -> Result<(), Box<dyn std::error::Error>> {
47 let base = temp_root("eggup-custom-validator");
48 let inputs = base.join("inputs");
49 let root = base.join("install");
50 fs::create_dir_all(&inputs)?;
51 fs::create_dir_all(root.join("bin"))?;
52 fs::write(inputs.join("app"), b"release-marker-42")?;
53 let digest = eggup_core::hash_file(&inputs.join("app"))?;
54 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
55 .with_integrity(IntegrityRequirement::Sha256(digest));
56 let plan = InstallPlan::new(
57 ProductId::new("demo")?,
58 ReleaseId::new("3.0.0")?,
59 &root,
60 ArtifactSet::single(member)?,
61 )?;
62 let validator = ContainsMarker {
63 member: MemberId::new("main")?,
64 marker: b"marker-42".to_vec(),
65 };
66 let receipt = plan
67 .prepare()?
68 .verify_integrity()?
69 .validate(&validator)?
70 .commit(CommitOwnership::new(
71 &ExistingAsOwnedVerifier,
72 AbsentPolicy::AllowCreate,
73 ))?;
74 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
75 let _ = fs::remove_dir_all(&base);
76 println!("custom validator commit: {:?}", receipt.disposition());
77 Ok(())
78}examples/ownership.rs (lines 36-41)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-ownership");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("app"), b"new-app")?;
31 fs::write(root.join("bin/app"), b"old-app")?;
32 let new_digest = eggup_core::hash_file(&inputs.join("app"))?;
33 let old_digest = eggup_core::hash_file(&root.join("bin/app"))?;
34 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
35 .with_integrity(IntegrityRequirement::Sha256(new_digest));
36 let plan = InstallPlan::new(
37 ProductId::new("demo")?,
38 ReleaseId::new("4.0.0")?,
39 &root,
40 ArtifactSet::single(member)?,
41 )?;
42 // Prove the live file is the expected old deployment; deny creation of
43 // absent destinations (replacement-only policy).
44 let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main")?, old_digest)]);
45 let receipt = plan
46 .prepare()?
47 .verify_integrity()?
48 .validate(&AllValidators::new())?
49 .commit(CommitOwnership::new(&verifier, AbsentPolicy::DenyCreate))?;
50 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
51 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
52 let _ = fs::remove_dir_all(&base);
53 println!("ownership-verified commit: {:?}", receipt.disposition());
54 Ok(())
55}examples/multi_member.rs (lines 46-51)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-multi-member");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("main"), b"new-main")?;
31 fs::write(inputs.join("helper"), b"new-helper")?;
32 fs::write(root.join("bin/main"), b"old-main")?;
33 fs::write(root.join("bin/helper"), b"old-helper")?;
34 let main_digest = eggup_core::hash_file(&inputs.join("main"))?;
35 let helper_digest = eggup_core::hash_file(&inputs.join("helper"))?;
36 let members = ArtifactSet::new(vec![
37 ArtifactMember::new(MemberId::new("main")?, inputs.join("main"), "bin/main")?
38 .with_integrity(IntegrityRequirement::Sha256(main_digest)),
39 ArtifactMember::new(
40 MemberId::new("helper")?,
41 inputs.join("helper"),
42 "bin/helper",
43 )?
44 .with_integrity(IntegrityRequirement::Sha256(helper_digest)),
45 ])?;
46 let plan = InstallPlan::new(
47 ProductId::new("bundle")?,
48 ReleaseId::new("2.0.0")?,
49 &root,
50 members,
51 )?;
52 let receipt = plan
53 .prepare()?
54 .verify_integrity()?
55 .validate(&AllValidators::new())?
56 .commit(CommitOwnership::new(
57 &ExistingAsOwnedVerifier,
58 AbsentPolicy::AllowCreate,
59 ))?;
60 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
61 assert_eq!(fs::read(root.join("bin/main"))?, b"new-main");
62 assert_eq!(fs::read(root.join("bin/helper"))?, b"new-helper");
63 let _ = fs::remove_dir_all(&base);
64 println!("multi-member commit: {:?}", receipt.disposition());
65 Ok(())
66}Sourcepub fn installation_root(&self) -> &Path
pub fn installation_root(&self) -> &Path
Returns the exact installation root used for destination resolution.
Sourcepub fn artifacts(&self) -> &ArtifactSet
pub fn artifacts(&self) -> &ArtifactSet
Returns the validated artifact set.
Sourcepub fn destination(&self, member: &MemberId) -> Result<PathBuf>
pub fn destination(&self, member: &MemberId) -> Result<PathBuf>
Resolves a member destination beneath the exact installation root.
Sourcepub fn prepare(self) -> Result<PreparedTransaction>
pub fn prepare(self) -> Result<PreparedTransaction>
Copies every member into private owned stage state without mutating a destination.
Examples found in repository?
examples/one_member.rs (line 42)
25fn main() -> Result<(), Box<dyn std::error::Error>> {
26 let base = temp_root("eggup-one-member");
27 let inputs = base.join("inputs");
28 let root = base.join("install");
29 fs::create_dir_all(inputs.join("bin"))?;
30 fs::create_dir_all(root.join("bin"))?;
31 fs::write(inputs.join("app"), b"new-app")?;
32 let digest = eggup_core::hash_file(&inputs.join("app"))?;
33 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
34 .with_integrity(IntegrityRequirement::Sha256(digest));
35 let plan = InstallPlan::new(
36 ProductId::new("demo")?,
37 ReleaseId::new("1.0.0")?,
38 &root,
39 ArtifactSet::single(member)?,
40 )?;
41 let receipt = plan
42 .prepare()?
43 .verify_integrity()?
44 .validate(&AllValidators::new())?
45 .commit(CommitOwnership::new(
46 &ExistingAsOwnedVerifier,
47 AbsentPolicy::AllowCreate,
48 ))?;
49 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
50 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
51 let _ = fs::remove_dir_all(&base);
52 println!("one-member commit: {:?}", receipt.disposition());
53 Ok(())
54}More examples
examples/custom_validator.rs (line 67)
46fn main() -> Result<(), Box<dyn std::error::Error>> {
47 let base = temp_root("eggup-custom-validator");
48 let inputs = base.join("inputs");
49 let root = base.join("install");
50 fs::create_dir_all(&inputs)?;
51 fs::create_dir_all(root.join("bin"))?;
52 fs::write(inputs.join("app"), b"release-marker-42")?;
53 let digest = eggup_core::hash_file(&inputs.join("app"))?;
54 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
55 .with_integrity(IntegrityRequirement::Sha256(digest));
56 let plan = InstallPlan::new(
57 ProductId::new("demo")?,
58 ReleaseId::new("3.0.0")?,
59 &root,
60 ArtifactSet::single(member)?,
61 )?;
62 let validator = ContainsMarker {
63 member: MemberId::new("main")?,
64 marker: b"marker-42".to_vec(),
65 };
66 let receipt = plan
67 .prepare()?
68 .verify_integrity()?
69 .validate(&validator)?
70 .commit(CommitOwnership::new(
71 &ExistingAsOwnedVerifier,
72 AbsentPolicy::AllowCreate,
73 ))?;
74 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
75 let _ = fs::remove_dir_all(&base);
76 println!("custom validator commit: {:?}", receipt.disposition());
77 Ok(())
78}examples/ownership.rs (line 46)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-ownership");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("app"), b"new-app")?;
31 fs::write(root.join("bin/app"), b"old-app")?;
32 let new_digest = eggup_core::hash_file(&inputs.join("app"))?;
33 let old_digest = eggup_core::hash_file(&root.join("bin/app"))?;
34 let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
35 .with_integrity(IntegrityRequirement::Sha256(new_digest));
36 let plan = InstallPlan::new(
37 ProductId::new("demo")?,
38 ReleaseId::new("4.0.0")?,
39 &root,
40 ArtifactSet::single(member)?,
41 )?;
42 // Prove the live file is the expected old deployment; deny creation of
43 // absent destinations (replacement-only policy).
44 let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main")?, old_digest)]);
45 let receipt = plan
46 .prepare()?
47 .verify_integrity()?
48 .validate(&AllValidators::new())?
49 .commit(CommitOwnership::new(&verifier, AbsentPolicy::DenyCreate))?;
50 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
51 assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
52 let _ = fs::remove_dir_all(&base);
53 println!("ownership-verified commit: {:?}", receipt.disposition());
54 Ok(())
55}examples/multi_member.rs (line 53)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25 let base = temp_root("eggup-multi-member");
26 let inputs = base.join("inputs");
27 let root = base.join("install");
28 fs::create_dir_all(&inputs)?;
29 fs::create_dir_all(root.join("bin"))?;
30 fs::write(inputs.join("main"), b"new-main")?;
31 fs::write(inputs.join("helper"), b"new-helper")?;
32 fs::write(root.join("bin/main"), b"old-main")?;
33 fs::write(root.join("bin/helper"), b"old-helper")?;
34 let main_digest = eggup_core::hash_file(&inputs.join("main"))?;
35 let helper_digest = eggup_core::hash_file(&inputs.join("helper"))?;
36 let members = ArtifactSet::new(vec![
37 ArtifactMember::new(MemberId::new("main")?, inputs.join("main"), "bin/main")?
38 .with_integrity(IntegrityRequirement::Sha256(main_digest)),
39 ArtifactMember::new(
40 MemberId::new("helper")?,
41 inputs.join("helper"),
42 "bin/helper",
43 )?
44 .with_integrity(IntegrityRequirement::Sha256(helper_digest)),
45 ])?;
46 let plan = InstallPlan::new(
47 ProductId::new("bundle")?,
48 ReleaseId::new("2.0.0")?,
49 &root,
50 members,
51 )?;
52 let receipt = plan
53 .prepare()?
54 .verify_integrity()?
55 .validate(&AllValidators::new())?
56 .commit(CommitOwnership::new(
57 &ExistingAsOwnedVerifier,
58 AbsentPolicy::AllowCreate,
59 ))?;
60 assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
61 assert_eq!(fs::read(root.join("bin/main"))?, b"new-main");
62 assert_eq!(fs::read(root.join("bin/helper"))?, b"new-helper");
63 let _ = fs::remove_dir_all(&base);
64 println!("multi-member commit: {:?}", receipt.disposition());
65 Ok(())
66}examples/receipts.rs (line 65)
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62 // 1. Committed: success.
63 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 // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78 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 // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103 // prove either generation is coherent. It is produced by rollback failure
104 // or unclean finalization; operators must inspect `recovery_path` and the
105 // preserved lock. See `docs/transaction.md` for the full contract.
106 println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107 Ok(())
108}Trait Implementations§
Source§impl Clone for InstallPlan
impl Clone for InstallPlan
Source§impl Debug for InstallPlan
impl Debug for InstallPlan
impl Eq for InstallPlan
Source§impl PartialEq for InstallPlan
impl PartialEq for InstallPlan
impl StructuralPartialEq for InstallPlan
Auto Trait Implementations§
impl Freeze for InstallPlan
impl RefUnwindSafe for InstallPlan
impl Send for InstallPlan
impl Sync for InstallPlan
impl Unpin for InstallPlan
impl UnsafeUnpin for InstallPlan
impl UnwindSafe for InstallPlan
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more