| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
use crate::config::{Archive, Config}; |
| 22 |
use crate::domain::{AppId, Target, Version}; |
| 23 |
use ops_exec::{CapabilitySet, Executor, LocalExec, SshExec, SyncOpts}; |
| 24 |
use std::path::{Path, PathBuf}; |
| 25 |
use std::sync::Arc; |
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
pub fn target_dir(root: &Path, app: &AppId, version: &Version, target: Target) -> PathBuf { |
| 35 |
root.join(app.as_str()) |
| 36 |
.join(version.to_string()) |
| 37 |
.join(target_slug(target)) |
| 38 |
} |
| 39 |
|
| 40 |
|
| 41 |
pub fn target_slug(target: Target) -> String { |
| 42 |
target.to_string().replace('/', "-") |
| 43 |
} |
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
fn transport(archive: &Archive) -> Arc<dyn Executor> { |
| 53 |
const NONE: [&str; 0] = []; |
| 54 |
let caps = CapabilitySet::from_tokens(NONE, NONE); |
| 55 |
if archive.host == "local" || archive.host.is_empty() { |
| 56 |
return Arc::new(LocalExec::new(caps)); |
| 57 |
} |
| 58 |
Arc::new(SshExec::new(archive.host.clone(), caps)) |
| 59 |
} |
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
pub async fn deposit( |
| 69 |
cfg: &Config, |
| 70 |
local_dir: &Path, |
| 71 |
app: &AppId, |
| 72 |
version: &Version, |
| 73 |
target: Target, |
| 74 |
) -> anyhow::Result<()> { |
| 75 |
let Some(archive) = &cfg.archive else { |
| 76 |
return Ok(()); |
| 77 |
}; |
| 78 |
let dest = target_dir(&archive.root, app, version, target); |
| 79 |
transport(archive) |
| 80 |
.push_dir(local_dir, &dest, &SyncOpts::archive_deposit()) |
| 81 |
.await |
| 82 |
.map_err(|e| { |
| 83 |
anyhow::anyhow!( |
| 84 |
"depositing {} at {}:{}: {e}", |
| 85 |
local_dir.display(), |
| 86 |
archive.host, |
| 87 |
dest.display() |
| 88 |
) |
| 89 |
})?; |
| 90 |
tracing::info!( |
| 91 |
%app, %target, %version, host = %archive.host, dest = %dest.display(), |
| 92 |
"deposited artifacts in the archive" |
| 93 |
); |
| 94 |
Ok(()) |
| 95 |
} |
| 96 |
|
| 97 |
#[cfg(test)] |
| 98 |
mod tests { |
| 99 |
use super::*; |
| 100 |
|
| 101 |
fn app() -> AppId { |
| 102 |
AppId::new("goingson") |
| 103 |
} |
| 104 |
fn version() -> Version { |
| 105 |
"1.4.0".parse().unwrap() |
| 106 |
} |
| 107 |
|
| 108 |
#[test] |
| 109 |
fn the_archive_path_is_per_app_version_and_target() { |
| 110 |
let dir = target_dir( |
| 111 |
Path::new("/var/lib/bento/artifacts"), |
| 112 |
&app(), |
| 113 |
&version(), |
| 114 |
"macos/aarch64".parse().unwrap(), |
| 115 |
); |
| 116 |
assert_eq!( |
| 117 |
dir, |
| 118 |
Path::new("/var/lib/bento/artifacts/goingson/1.4.0/macos-aarch64") |
| 119 |
); |
| 120 |
} |
| 121 |
|
| 122 |
|
| 123 |
|
| 124 |
|
| 125 |
#[test] |
| 126 |
fn a_target_slug_carries_no_path_separator() { |
| 127 |
let slug = target_slug("macos/aarch64".parse().unwrap()); |
| 128 |
assert!(!slug.contains('/'), "{slug} must be a single component"); |
| 129 |
assert_eq!(slug, "macos-aarch64"); |
| 130 |
} |
| 131 |
|
| 132 |
|
| 133 |
|
| 134 |
|
| 135 |
#[test] |
| 136 |
fn sibling_targets_do_not_share_a_directory() { |
| 137 |
let root = Path::new("/a"); |
| 138 |
let linux = target_dir(root, &app(), &version(), "linux/x86_64".parse().unwrap()); |
| 139 |
let macos = target_dir(root, &app(), &version(), "macos/aarch64".parse().unwrap()); |
| 140 |
assert_ne!(linux, macos); |
| 141 |
assert_eq!(linux.parent(), macos.parent()); |
| 142 |
} |
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
#[tokio::test] |
| 147 |
async fn no_archive_configured_deposits_nothing() { |
| 148 |
let tmp = tempfile::tempdir().unwrap(); |
| 149 |
let cfg = Config::for_tests(tmp.path()); |
| 150 |
assert!(cfg.archive.is_none()); |
| 151 |
deposit( |
| 152 |
&cfg, |
| 153 |
tmp.path(), |
| 154 |
&app(), |
| 155 |
&version(), |
| 156 |
"linux/x86_64".parse().unwrap(), |
| 157 |
) |
| 158 |
.await |
| 159 |
.expect("a no-op cannot fail"); |
| 160 |
} |
| 161 |
|
| 162 |
|
| 163 |
|
| 164 |
|
| 165 |
#[tokio::test] |
| 166 |
async fn a_local_deposit_lands_at_the_versioned_path() { |
| 167 |
let tmp = tempfile::tempdir().unwrap(); |
| 168 |
let src = tmp.path().join("collected"); |
| 169 |
std::fs::create_dir_all(&src).unwrap(); |
| 170 |
std::fs::write(src.join("goingson_1.4.0.AppImage"), b"bytes").unwrap(); |
| 171 |
|
| 172 |
let mut cfg = Config::for_tests(tmp.path()); |
| 173 |
cfg.archive = Some(Archive { |
| 174 |
host: "local".into(), |
| 175 |
root: tmp.path().join("archive"), |
| 176 |
}); |
| 177 |
let target: Target = "linux/x86_64".parse().unwrap(); |
| 178 |
deposit(&cfg, &src, &app(), &version(), target) |
| 179 |
.await |
| 180 |
.unwrap(); |
| 181 |
|
| 182 |
let landed = tmp |
| 183 |
.path() |
| 184 |
.join("archive/goingson/1.4.0/linux-x86_64/goingson_1.4.0.AppImage"); |
| 185 |
assert!(landed.exists(), "{} must exist", landed.display()); |
| 186 |
assert_eq!(std::fs::read(&landed).unwrap(), b"bytes"); |
| 187 |
} |
| 188 |
|
| 189 |
|
| 190 |
|
| 191 |
#[tokio::test] |
| 192 |
async fn a_second_target_leaves_the_first_alone() { |
| 193 |
let tmp = tempfile::tempdir().unwrap(); |
| 194 |
let mut cfg = Config::for_tests(tmp.path()); |
| 195 |
cfg.archive = Some(Archive { |
| 196 |
host: "local".into(), |
| 197 |
root: tmp.path().join("archive"), |
| 198 |
}); |
| 199 |
|
| 200 |
for (target, file) in [ |
| 201 |
("linux/x86_64", "goingson_1.4.0.AppImage"), |
| 202 |
("macos/aarch64", "goingson_1.4.0.dmg"), |
| 203 |
] { |
| 204 |
let src = tmp.path().join(format!("collected-{file}")); |
| 205 |
std::fs::create_dir_all(&src).unwrap(); |
| 206 |
std::fs::write(src.join(file), b"bytes").unwrap(); |
| 207 |
deposit(&cfg, &src, &app(), &version(), target.parse().unwrap()) |
| 208 |
.await |
| 209 |
.unwrap(); |
| 210 |
} |
| 211 |
|
| 212 |
let root = tmp.path().join("archive/goingson/1.4.0"); |
| 213 |
assert!(root.join("linux-x86_64/goingson_1.4.0.AppImage").exists()); |
| 214 |
assert!(root.join("macos-aarch64/goingson_1.4.0.dmg").exists()); |
| 215 |
} |
| 216 |
} |
| 217 |
|