release_kit/stage/
read.rs1use std::path::Path;
13
14use camino::Utf8Path;
15
16use super::{Parameters, RECEIPT_NAME, Receipt};
17use crate::diagnostic::{Diagnostic, Reason};
18use crate::error::RkError;
19use crate::landing::Params;
20use crate::profile::Inputs;
21
22pub fn load(root: &Utf8Path, canonical_target: &Path) -> Result<Receipt, RkError> {
30 let path = root.join(RECEIPT_NAME);
31 let receipt: Receipt = std::fs::read(&path)
32 .ok()
33 .and_then(|bytes| serde_json::from_slice(&bytes).ok())
34 .ok_or_else(|| {
35 RkError::missing(
36 Diagnostic::new(
37 Reason::TargetNotFound,
38 format!("{path} is no stage receipt this binary reads"),
39 )
40 .expected("the root of a stage `rk stage` wrote")
41 .action("rk stage --target <target> writes one"),
42 )
43 })?;
44 if receipt.rk_version != env!("CARGO_PKG_VERSION") {
45 return Err(RkError::refusal(
46 Diagnostic::new(
47 Reason::StateDrift,
48 format!(
49 "the stage at {root} was written by rk {}, and this is rk {}; nothing was written",
50 receipt.rk_version,
51 env!("CARGO_PKG_VERSION")
52 ),
53 )
54 .expected("a stage this binary wrote")
55 .action("stage again with this binary, then land from that stage")
56 .target_state("unchanged"),
57 ));
58 }
59 let target = canonical_target.display().to_string();
60 if receipt.target != target {
61 return Err(RkError::refusal(
62 Diagnostic::new(
63 Reason::StateDrift,
64 format!(
65 "the stage at {root} was taken for {}, not {target}; nothing was written",
66 receipt.target
67 ),
68 )
69 .expected("a stage taken for this target")
70 .action(format!("rk stage --target {target} writes one"))
71 .target_state("unchanged"),
72 ));
73 }
74 Ok(receipt)
75}
76
77#[must_use]
79pub fn inputs(parameters: &Parameters) -> Inputs<'_> {
80 let release = ¶meters.profile.release;
81 Inputs {
82 technologies: ¶meters.profile.technologies,
83 forge: parameters.profile.forge.as_deref(),
84 repo: (!parameters.repo.is_empty()).then_some(parameters.repo.as_str()),
85 release_mode: Some(release.mode),
86 release_driver: release.driver.as_deref(),
87 style: release.style,
88 trunk: Some(parameters.git.trunk.as_str()),
89 checkout_mode: Some(parameters.git.checkout_mode),
90 integration: Some(parameters.git.integration),
91 merge_form: Some(parameters.git.merge_form),
92 required_check: (!parameters.required_check.is_empty())
93 .then_some(parameters.required_check.as_str()),
94 required_workflow: (!parameters.required_workflow.is_empty())
95 .then_some(parameters.required_workflow.as_str()),
96 nix: Some(parameters.capabilities.nix_packaging),
97 reporting_policy: Some(parameters.capabilities.reporting_policy),
98 scorecard: Some(parameters.capabilities.scorecard),
99 workflow_guards: parameters.capabilities.workflow_guards,
100 code_scanning: Some(parameters.capabilities.code_scanning),
101 }
102}
103
104pub fn confirm(params: &Params, receipt: &Receipt, root: &Utf8Path) -> Result<(), RkError> {
112 let resolved = serde_json::to_value(Parameters::from(params)).map_err(anyhow::Error::from)?;
113 let staged = serde_json::to_value(&receipt.parameters).map_err(anyhow::Error::from)?;
114 if resolved == staged {
115 return Ok(());
116 }
117 Err(RkError::refusal(
118 Diagnostic::new(
119 Reason::StateDrift,
120 format!(
121 "the target's answers moved since the stage at {root} was written, and nothing was written"
122 ),
123 )
124 .expected("the answers the stage resolved")
125 .action("stage again, compare, then land from the new stage")
126 .target_state("unchanged"),
127 ))
128}
129
130#[must_use]
133pub fn answered_twice() -> RkError {
134 RkError::Usage(
135 "--stage carries every answer the stage resolved; pass the answers to rk stage instead"
136 .to_owned(),
137 )
138}