1mod artifact;
13mod deploy_render;
14mod heartbeat;
15mod postgres;
16mod publish;
17mod release;
18mod watchdog;
19
20use anyhow::{Context, Result, bail};
21use clap::{Parser, Subcommand};
22
23use artifact::Kind;
24use heartbeat::Heartbeat;
25
26#[derive(Parser)]
27#[command(about = "nomisync CI tasks")]
28struct Cli {
29 #[command(subcommand)]
30 command: Command,
31}
32
33#[derive(Subcommand)]
34enum Command {
35 Postgres {
37 #[arg(required = true, trailing_var_arg = true)]
39 argv: Vec<String>,
40 },
41 Coverage,
43 Release,
45 Finalize,
47}
48
49fn main() -> Result<()> {
50 match Cli::parse().command {
51 Command::Postgres { argv } => with_postgres(&argv),
52 Command::Coverage => produce(Kind::Coverage),
53 Command::Release => produce(Kind::Release),
54 Command::Finalize => finalize(),
55 }
56}
57
58fn finalize() -> Result<()> {
65 let identity = artifact::Identity::from_env()?;
66 let store = publish::Store::from_env()?;
67 let work =
68 tempfile::TempDir::with_prefix("nomisync-finalize").context("cannot make a temp dir")?;
69
70 let metas: Vec<artifact::Meta> = Kind::ALL
71 .iter()
72 .map(|kind| store.fetch_meta(*kind, &identity, work.path()))
73 .collect::<Result<_>>()?;
74
75 for meta in &metas {
76 let local = store.verify_object(meta, &identity, work.path())?;
77 println!(
78 ">> verified {} ({} bytes)",
79 local.file_name().unwrap_or_default().to_string_lossy(),
80 meta.bytes
81 );
82 }
83
84 let [coverage, release] = metas.as_slice() else {
85 bail!(
86 "expected exactly {} artifacts, found {}",
87 Kind::ALL.len(),
88 metas.len()
89 );
90 };
91 artifact::check_pair(coverage, release).map_err(|err| anyhow::anyhow!("{err}"))?;
94
95 store.write_manifest(&metas, &identity, work.path())?;
96 println!(
97 ">> manifest written for {} run {}",
98 identity.commit, identity.run_id
99 );
100 Ok(())
101}
102
103fn with_artifacts() -> bool {
106 std::env::var("WITH_ARTIFACTS").as_deref() == Ok("1")
107}
108
109fn produce(kind: Kind) -> Result<()> {
110 let heartbeat = Heartbeat::start();
114 heartbeat.phase("starting postgres");
115
116 let runtime = tokio::runtime::Builder::new_current_thread()
117 .enable_all()
118 .build()
119 .context("cannot build a tokio runtime")?;
120 let database = runtime.block_on(postgres::start())?;
123 let url = database.database_url();
124
125 match kind {
126 Kind::Coverage => release::coverage(&heartbeat, &url)?,
127 Kind::Release => release::build(&heartbeat, &url)?,
128 }
129 release::package(kind, &heartbeat, with_artifacts())
130}
131
132fn with_postgres(argv: &[String]) -> Result<()> {
136 let runtime = tokio::runtime::Builder::new_current_thread()
137 .enable_all()
138 .build()
139 .context("cannot build a tokio runtime")?;
140 let database = runtime.block_on(postgres::start())?;
141
142 let (program, args) = argv.split_first().context("no command given")?;
143 let status = std::process::Command::new(program)
144 .args(args)
145 .env("DATABASE_URL", database.database_url())
146 .status()
147 .with_context(|| format!("failed to run {program}"))?;
148
149 if !status.success() {
150 bail!("{program} exited with {status}");
151 }
152 Ok(())
153}