| 454 |
454 |
|
/// atomic same-filesystem rename); getting the bytes there is the transport's
|
| 455 |
455 |
|
/// job, not this function's.
|
| 456 |
456 |
|
#[allow(clippy::too_many_arguments)]
|
| 457 |
|
- |
pub async fn intake_and_gate(
|
| 458 |
|
- |
pool: SqlitePool,
|
| 459 |
|
- |
cfg: Arc<AppConfig>,
|
| 460 |
|
- |
topo: Arc<Topology>,
|
| 461 |
|
- |
staged: PathBuf,
|
| 462 |
|
- |
record_json: String,
|
| 463 |
|
- |
events: crate::events::EventTx,
|
|
457 |
+ |
pub async fn accept_intake(
|
|
458 |
+ |
pool: &SqlitePool,
|
|
459 |
+ |
cfg: &AppConfig,
|
|
460 |
+ |
staged: &Path,
|
|
461 |
+ |
record_json: &str,
|
| 464 |
462 |
|
run_id: RunId,
|
| 465 |
|
- |
deploy_lock: Arc<tokio::sync::Mutex<()>>,
|
| 466 |
|
- |
) -> Result<()> {
|
| 467 |
|
- |
crate::runs::set_phase(&pool, run_id, crate::runs::Phase::Staging)
|
|
463 |
+ |
) -> Result<Published> {
|
|
464 |
+ |
crate::runs::set_phase(pool, run_id, crate::runs::Phase::Staging)
|
| 468 |
465 |
|
.await
|
| 469 |
466 |
|
.ok();
|
| 470 |
467 |
|
|
| 471 |
|
- |
let accepted = crate::intake::accept(&cfg.release_root, &staged, &record_json)
|
|
468 |
+ |
let accepted = crate::intake::accept(&cfg.release_root, staged, record_json)
|
| 472 |
469 |
|
.await
|
| 473 |
470 |
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
| 474 |
471 |
|
|
| 491 |
488 |
|
)
|
| 492 |
489 |
|
})?;
|
| 493 |
490 |
|
|
| 494 |
|
- |
crate::runs::set_version(&pool, run_id, &version).await.ok();
|
|
491 |
+ |
crate::runs::set_version(pool, run_id, &version).await.ok();
|
| 495 |
492 |
|
upsert_version_row(
|
| 496 |
|
- |
&pool,
|
|
493 |
+ |
pool,
|
| 497 |
494 |
|
&cfg.id,
|
| 498 |
495 |
|
&version,
|
| 499 |
496 |
|
&git_sha,
|
| 507 |
504 |
|
digest_full: accepted.record.digest.to_string(),
|
| 508 |
505 |
|
platform: Some(platform),
|
| 509 |
506 |
|
};
|
| 510 |
|
- |
record_identity(&pool, &cfg, &published, run_id).await?;
|
|
507 |
+ |
record_identity(pool, cfg, &published, run_id).await?;
|
|
508 |
+ |
Ok(published)
|
|
509 |
+ |
}
|
| 511 |
510 |
|
|
| 512 |
|
- |
// An intake carries no worktree, and the gates that need one refuse rather
|
| 513 |
|
- |
// than resolve against nothing. That is the boundary showing up in the type:
|
| 514 |
|
- |
// artifact-scoped gates belong to the builder (wiki [[sando-bento-boundary]]),
|
| 515 |
|
- |
// so a tier that asks Sando to re-run them against an accepted artifact is
|
| 516 |
|
- |
// misconfigured and should be told so.
|
|
511 |
+ |
/// Gate an artifact that has already been accepted.
|
|
512 |
+ |
///
|
|
513 |
+ |
/// Split from [`accept_intake`] so the two can be answered on different clocks.
|
|
514 |
+ |
/// Acceptance is fast and is the producer's business — it either believes the
|
|
515 |
+ |
/// bytes or names the file that drifted — so the caller waits for it and gets
|
|
516 |
+ |
/// the verdict. Gating is Sando's business and can take an hour, so the caller
|
|
517 |
+ |
/// does not.
|
|
518 |
+ |
///
|
|
519 |
+ |
/// An intake carries no worktree, and the gates that need one refuse rather than
|
|
520 |
+ |
/// resolve against nothing. That is the boundary showing up in the type:
|
|
521 |
+ |
/// artifact-scoped gates belong to the builder (wiki [[sando-bento-boundary]]),
|
|
522 |
+ |
/// so a tier that asks Sando to re-run them against an accepted artifact is
|
|
523 |
+ |
/// misconfigured and should be told so.
|
|
524 |
+ |
pub async fn gate_intake(
|
|
525 |
+ |
pool: SqlitePool,
|
|
526 |
+ |
cfg: Arc<AppConfig>,
|
|
527 |
+ |
topo: Arc<Topology>,
|
|
528 |
+ |
published: Published,
|
|
529 |
+ |
events: crate::events::EventTx,
|
|
530 |
+ |
run_id: RunId,
|
|
531 |
+ |
deploy_lock: Arc<tokio::sync::Mutex<()>>,
|
|
532 |
+ |
) -> Result<()> {
|
| 517 |
533 |
|
record_and_gate(
|
| 518 |
534 |
|
pool,
|
| 519 |
535 |
|
cfg,
|
| 603 |
619 |
|
|
| 604 |
620 |
|
/// A bundle that has been hashed and published content-addressed. Both paths
|
| 605 |
621 |
|
/// produce one; nothing downstream can tell them apart.
|
| 606 |
|
- |
struct Published {
|
|
622 |
+ |
///
|
|
623 |
+ |
/// Public because the intake route now hands one from `accept_intake` to
|
|
624 |
+ |
/// `gate_intake`: proving the bytes answers the producer, gating them does not,
|
|
625 |
+ |
/// so the two run on different clocks and the value passes between them.
|
|
626 |
+ |
#[derive(Debug)]
|
|
627 |
+ |
pub struct Published {
|
| 607 |
628 |
|
version: Version,
|
| 608 |
629 |
|
released: PathBuf,
|
| 609 |
630 |
|
digest_full: String,
|
| 853 |
874 |
|
#[cfg(test)]
|
| 854 |
875 |
|
mod tests {
|
| 855 |
876 |
|
use super::{
|
| 856 |
|
- |
BuildArtifact, check_build_host, checkout_aux_repos, intake_and_gate, runtime_hostname,
|
| 857 |
|
- |
stage_and_gate, tail,
|
|
877 |
+ |
BuildArtifact, accept_intake, check_build_host, checkout_aux_repos, gate_intake,
|
|
878 |
+ |
runtime_hostname, stage_and_gate, tail,
|
| 858 |
879 |
|
};
|
| 859 |
880 |
|
use crate::config::{AppConfig, TestTarget};
|
| 860 |
881 |
|
use crate::domain::{GitSha, RunId, Version};
|
| 1050 |
1071 |
|
.unwrap();
|
| 1051 |
1072 |
|
let record = record_for(&staged, "1.2.3", "linux/aarch64").await;
|
| 1052 |
1073 |
|
|
| 1053 |
|
- |
intake_and_gate(
|
|
1074 |
+ |
// Two calls now, on purpose: the route answers its caller on the first
|
|
1075 |
+ |
// and spawns the second. Acceptance is what the producer waits for.
|
|
1076 |
+ |
let published = accept_intake(&pool, &cfg, &staged, &record, run_id)
|
|
1077 |
+ |
.await
|
|
1078 |
+ |
.expect("the bytes are believed");
|
|
1079 |
+ |
gate_intake(
|
| 1054 |
1080 |
|
pool.clone(),
|
| 1055 |
1081 |
|
cfg.clone(),
|
| 1056 |
1082 |
|
topo,
|
| 1057 |
|
- |
staged.clone(),
|
| 1058 |
|
- |
record,
|
|
1083 |
+ |
published,
|
| 1059 |
1084 |
|
crate::events::channel(),
|
| 1060 |
1085 |
|
run_id,
|
| 1061 |
1086 |
|
deploy_lock,
|
| 1117 |
1142 |
|
.await
|
| 1118 |
1143 |
|
.unwrap();
|
| 1119 |
1144 |
|
|
| 1120 |
|
- |
let err = intake_and_gate(
|
| 1121 |
|
- |
pool.clone(),
|
| 1122 |
|
- |
cfg,
|
| 1123 |
|
- |
topo,
|
| 1124 |
|
- |
staged.clone(),
|
| 1125 |
|
- |
record,
|
| 1126 |
|
- |
crate::events::channel(),
|
| 1127 |
|
- |
run_id,
|
| 1128 |
|
- |
deploy_lock,
|
| 1129 |
|
- |
)
|
| 1130 |
|
- |
.await
|
| 1131 |
|
- |
.expect_err("a drifted bundle is refused");
|
|
1145 |
+ |
// Refused by ACCEPTANCE, not by gating — which is what lets the route
|
|
1146 |
+ |
// answer the producer with the refusal instead of a `202`-shaped lie.
|
|
1147 |
+ |
let _ = (&topo, &deploy_lock);
|
|
1148 |
+ |
let err = accept_intake(&pool, &cfg, &staged, &record, run_id)
|
|
1149 |
+ |
.await
|
|
1150 |
+ |
.expect_err("a drifted bundle is refused");
|
| 1132 |
1151 |
|
assert!(err.to_string().contains("makenotwork"), "{err}");
|
| 1133 |
1152 |
|
|
| 1134 |
1153 |
|
// Nothing advanced, and the bytes were left where they were.
|