Author:
Hash:
Timestamp:
+217 -107 +/-16 browse
Kevin Schoon [me@kevinschoon.com]
f37d02f238847605fe3ef17ac01986df998f1bd7
Fri, 12 Jun 2026 13:11:15 +0000 (1 month ago)
| 1 | diff --git a/.ayllu-build.jsonnet b/.ayllu-build.jsonnet |
| 2 | index 1057b21..fa4e9ff 100644 |
| 3 | --- a/.ayllu-build.jsonnet |
| 4 | +++ b/.ayllu-build.jsonnet |
| 5 | @@ -2,6 +2,7 @@ local target = 'x86_64-alpine-linux-musl'; |
| 6 | local build_image = 'ayllu-arch'; |
| 7 | local version = '0.5.1'; |
| 8 | local arch_package = 'ayllu-dev-' + version + '-1-x86_64.pkg.tar.zst'; |
| 9 | + local cargo_target_dir = '/home/ayllu/cargo-target'; |
| 10 | |
| 11 | local cargo_cache = { |
| 12 | name: 'cargo-cache', |
| 13 | @@ -10,12 +11,11 @@ local cargo_cache = { |
| 14 | |
| 15 | local build_cache = { |
| 16 | name: 'build-cache', |
| 17 | - path: '/src/target', |
| 18 | + path: '/home/ayllu/cargo-target', |
| 19 | transient: true, |
| 20 | }; |
| 21 | |
| 22 | local binaries = [ |
| 23 | - 'ayllu-init', |
| 24 | 'ayllu-build', |
| 25 | 'ayllu-keys', |
| 26 | 'ayllu-migrate', |
| 27 | @@ -32,7 +32,11 @@ local subcommands = [ |
| 28 | |
| 29 | local compile_binary(name) = { |
| 30 | name: 'Cargo Build ' + name, |
| 31 | - input: 'cargo build --release --bin=' + name, |
| 32 | + input: 'cargo build --frozen --release --bin=' + name, |
| 33 | + environment: { |
| 34 | + AYLLU_VERSION: version, |
| 35 | + CARGO_TARGET_DIR: cargo_target_dir, |
| 36 | + } |
| 37 | }; |
| 38 | |
| 39 | local zsh_complete(name) = { |
| 40 | @@ -53,6 +57,10 @@ local man_page(name, section) = { |
| 41 | local short_step(name, input) = { |
| 42 | name: name, |
| 43 | input: input, |
| 44 | + environment: { |
| 45 | + AYLLU_VERSION: version, |
| 46 | + CARGO_TARGET_DIR: cargo_target_dir, |
| 47 | + }, |
| 48 | }; |
| 49 | |
| 50 | { |
| 51 | @@ -66,53 +74,64 @@ local short_step(name, input) = { |
| 52 | cache: [cargo_cache, build_cache], |
| 53 | }, |
| 54 | { |
| 55 | - name: 'Distribution', |
| 56 | + name: 'Metadata', |
| 57 | + image: build_image, |
| 58 | + networking: true, |
| 59 | + steps: [ |
| 60 | + short_step('Fetch', 'cargo fetch --locked'), |
| 61 | + short_step('Metadata', 'cargo metadata > metadata.json'), |
| 62 | + ], |
| 63 | + cache: [cargo_cache, build_cache], |
| 64 | + outputs: [{ |
| 65 | + path: 'metadata.json', |
| 66 | + sha256sum: null, |
| 67 | + }], |
| 68 | depends_on: ['Lint'], |
| 69 | + }, |
| 70 | + { |
| 71 | + name: 'Test', |
| 72 | image: build_image, |
| 73 | + steps: [ |
| 74 | + short_step('Cargo Test', 'cargo test --frozen --release'), |
| 75 | + ], |
| 76 | cache: [cargo_cache, build_cache], |
| 77 | - steps: |
| 78 | - std.flattenArrays( |
| 79 | - [ |
| 80 | - [ |
| 81 | - { |
| 82 | - name: 'Man', |
| 83 | - input: 'cargo run --package xtask -- gen_man', |
| 84 | - }, |
| 85 | - { |
| 86 | - name: 'Completion', |
| 87 | - input: 'cargo run --package xtask -- complete', |
| 88 | - }, |
| 89 | - ], |
| 90 | - [ |
| 91 | - short_step('Cargo Test', 'cargo test'), |
| 92 | - short_step('Cargo Clippy', 'cargo clippy'), |
| 93 | - ], |
| 94 | - [ |
| 95 | - compile_binary(binary) |
| 96 | - for binary in binaries |
| 97 | - ], |
| 98 | - [ |
| 99 | - { |
| 100 | - name: 'Arch Package', |
| 101 | - input: 'VERSION=' + version + ' makepkg -f', |
| 102 | - }, |
| 103 | - ], |
| 104 | - ] |
| 105 | - ), |
| 106 | - outputs: std.flattenArrays([ |
| 107 | - [zsh_complete(name) for name in binaries], |
| 108 | - [bash_complete(name) for name in binaries], |
| 109 | - [man_page(name, '1') for name in binaries], |
| 110 | - [man_page('ayllu', '7')], |
| 111 | - [{ path: arch_package }], |
| 112 | + depends_on: ['Lint'], |
| 113 | + }, |
| 114 | + { |
| 115 | + name: 'Compile Binaries', |
| 116 | + image: build_image, |
| 117 | + steps: std.flattenArrays( |
| 118 | [ |
| 119 | - { |
| 120 | - path: 'target/release/' + binary, |
| 121 | - sha256sum: null, |
| 122 | - } |
| 123 | - for binary in binaries |
| 124 | - ], |
| 125 | - ]), |
| 126 | + [ |
| 127 | + // NOTE: Somewhat oddly man pages are embedded in the ayllu-web binary |
| 128 | + // and so this task must always be ran before compiling ayllu-web. |
| 129 | + short_step('Man Pages', 'cargo run --frozen --package xtask -- gen_man'), |
| 130 | + ], |
| 131 | + [ |
| 132 | + compile_binary(binary) |
| 133 | + for binary in binaries |
| 134 | + ], |
| 135 | + ] |
| 136 | + ), |
| 137 | + cache: [cargo_cache, build_cache], |
| 138 | + depends_on: ['Test'], |
| 139 | }, |
| 140 | + { |
| 141 | + name: 'Arch Distribution', |
| 142 | + image: build_image, |
| 143 | + steps: [ |
| 144 | + short_step('Man Pages', 'cargo run --frozen --package xtask -- gen_man'), |
| 145 | + short_step('Shell Completion', 'cargo run --frozen --package xtask -- complete'), |
| 146 | + short_step('Makepkg', 'makepkg') |
| 147 | + ], |
| 148 | + cache: [cargo_cache, build_cache], |
| 149 | + depends_on: ['Compile Binaries'], |
| 150 | + outputs: [ |
| 151 | + { |
| 152 | + path: arch_package, |
| 153 | + sha256sum: null |
| 154 | + } |
| 155 | + ] |
| 156 | + } |
| 157 | ], |
| 158 | } |
| 159 | diff --git a/.gitignore b/.gitignore |
| 160 | index c1615dd..0f589be 100644 |
| 161 | --- a/.gitignore |
| 162 | +++ b/.gitignore |
| 163 | @@ -17,3 +17,7 @@ demo |
| 164 | *.zst |
| 165 | /src |
| 166 | pkg |
| 167 | + |
| 168 | + dist/completion/* |
| 169 | + dist/man/* |
| 170 | + !dist/man/.gitkeep |
| 171 | diff --git a/PKGBUILD b/PKGBUILD |
| 172 | index 4d58aa1..580281e 100644 |
| 173 | --- a/PKGBUILD |
| 174 | +++ b/PKGBUILD |
| 175 | @@ -1,14 +1,22 @@ |
| 176 | + # This is the PKGBUILD for Ayllu's internal build server. Upstream arch builds |
| 177 | + # should be managed seperately on the AUR but can use this file as a reference. |
| 178 | + # |
| 179 | pkgname=ayllu-dev |
| 180 | - pkgver="$VERSION" |
| 181 | + pkgver="$AYLLU_VERSION" |
| 182 | pkgrel=1 |
| 183 | pkgdesc="Ayllu Forge" |
| 184 | arch=("x86_64") |
| 185 | license=("AGPL3") |
| 186 | url="https://ayllu-forge.org/ayllu/ayllu" |
| 187 | - depends=() |
| 188 | + depends=( |
| 189 | + "fuse-overlayfs" |
| 190 | + ) |
| 191 | makedepends=() |
| 192 | provides=("ayllu") |
| 193 | - optdepends=() |
| 194 | + optdepends=( |
| 195 | + "crun" |
| 196 | + "runc" |
| 197 | + ) |
| 198 | source=() |
| 199 | |
| 200 | BINARIES=( |
| 201 | @@ -22,9 +30,9 @@ BINARIES=( |
| 202 | |
| 203 | prepare() { |
| 204 | for i in "${!BINARIES[@]}"; do |
| 205 | - cp -v "../target/release/${BINARIES[i]}" . |
| 206 | + cp -v "$CARGO_TARGET_DIR/release/${BINARIES[i]}" . |
| 207 | done |
| 208 | - cp -rv ../target/dist . |
| 209 | + cp -rv ../dist . |
| 210 | find dist/man -type f -exec gzip -f '{}' \; |
| 211 | cp -v ../LICENSE . |
| 212 | cp -v ../config.example.toml . |
| 213 | diff --git a/ayllu-build/src/error.rs b/ayllu-build/src/error.rs |
| 214 | index 0f70c65..9108c28 100644 |
| 215 | --- a/ayllu-build/src/error.rs |
| 216 | +++ b/ayllu-build/src/error.rs |
| 217 | @@ -10,6 +10,8 @@ pub enum Error { |
| 218 | RepositoryDoesNotContainManifest { path: PathBuf }, |
| 219 | #[error("Cannot read manifest from git repository: {0}")] |
| 220 | CannotReadManifestFromRepository(ayllu_git::Error), |
| 221 | + #[error("Cannot resolve HEAD: {0}")] |
| 222 | + CannotResolveHead(ayllu_git::Error), |
| 223 | #[error("Cannot find managed repository: {collection} {name}")] |
| 224 | CannotFindManagedRepository { collection: String, name: String }, |
| 225 | #[error("Preprocessor command: {program} {args:?} failed: {io_error}")] |
| 226 | diff --git a/ayllu-build/src/main.rs b/ayllu-build/src/main.rs |
| 227 | index d80b3d5..3cfcbb2 100644 |
| 228 | --- a/ayllu-build/src/main.rs |
| 229 | +++ b/ayllu-build/src/main.rs |
| 230 | @@ -1,4 +1,4 @@ |
| 231 | - use std::{path::Path, time::Duration}; |
| 232 | + use std::time::Duration; |
| 233 | |
| 234 | use tabled::builder::Builder; |
| 235 | use tracing::Level; |
| 236 | @@ -25,38 +25,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 237 | let cfg = config::load(cli.config.as_deref())?; |
| 238 | ayllu_logging::init(cli.level.unwrap_or(Level::INFO)); |
| 239 | match cli.command { |
| 240 | + Commands::Check { source } => { |
| 241 | + let source = Source::from_arg(source.as_deref())?; |
| 242 | + let resolved = source.read(&cfg)?; |
| 243 | + let (n_workflows, n_steps) = resolved.manifest.workflows.iter().fold( |
| 244 | + (0, 0), |
| 245 | + |(n_workflows, n_steps), workflow| { |
| 246 | + (n_workflows + 1_usize, n_steps + workflow.steps.len()) |
| 247 | + }, |
| 248 | + ); |
| 249 | + println!("Manifest contains {n_workflows} workflows and {n_steps} steps"); |
| 250 | + Ok(()) |
| 251 | + } |
| 252 | Commands::Evaluate { |
| 253 | source, |
| 254 | stdin, |
| 255 | name, |
| 256 | forward, |
| 257 | + debug, |
| 258 | } => { |
| 259 | let source = if stdin { |
| 260 | tracing::info!("Reading source from stdin for build {name:?}"); |
| 261 | Source::from_stdin()? |
| 262 | } else { |
| 263 | - match source { |
| 264 | - Some(path) => { |
| 265 | - let path = Path::new(&path); |
| 266 | - if path.is_file() { |
| 267 | - Source::Manifest { |
| 268 | - path: path.to_path_buf(), |
| 269 | - } |
| 270 | - } else { |
| 271 | - Source::Repository { |
| 272 | - path: path.to_path_buf(), |
| 273 | - reference: None, |
| 274 | - } |
| 275 | - } |
| 276 | - } |
| 277 | - None => Source::Repository { |
| 278 | - path: std::fs::canonicalize(Path::new("."))?, |
| 279 | - reference: None, |
| 280 | - }, |
| 281 | - } |
| 282 | + Source::from_arg(source.as_deref())? |
| 283 | }; |
| 284 | let runtime = RuntimeBuilder::default() |
| 285 | .config(cfg.clone()) |
| 286 | + .debug(debug) |
| 287 | .oci_runtime( |
| 288 | OciRuntimeBuilder::default() |
| 289 | .config(cfg.build.clone()) |
| 290 | @@ -75,6 +71,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 291 | } => { |
| 292 | let runtime = RuntimeBuilder::default() |
| 293 | .config(cfg.clone()) |
| 294 | + .debug(false) |
| 295 | .oci_runtime( |
| 296 | OciRuntimeBuilder::default() |
| 297 | .config(cfg.build.clone()) |
| 298 | diff --git a/ayllu-build/src/manifest.rs b/ayllu-build/src/manifest.rs |
| 299 | index 0254e59..80c2c2e 100644 |
| 300 | --- a/ayllu-build/src/manifest.rs |
| 301 | +++ b/ayllu-build/src/manifest.rs |
| 302 | @@ -8,11 +8,17 @@ use crate::error::Error; |
| 303 | |
| 304 | // pub type TestGraph = DiGraph<(Job, DiGraph<Step, i32>), i32>; |
| 305 | |
| 306 | + fn serde_false() -> bool { |
| 307 | + false |
| 308 | + } |
| 309 | + |
| 310 | #[derive(Serialize, Deserialize, Debug, Clone)] |
| 311 | pub struct Cache { |
| 312 | pub name: String, |
| 313 | pub path: PathBuf, |
| 314 | + #[serde(default = "serde_false")] |
| 315 | pub read_only: bool, |
| 316 | + #[serde(default = "serde_false")] |
| 317 | pub transient: bool, |
| 318 | } |
| 319 | |
| 320 | diff --git a/ayllu-build/src/oci_runtime.rs b/ayllu-build/src/oci_runtime.rs |
| 321 | index 8462e15..8210748 100644 |
| 322 | --- a/ayllu-build/src/oci_runtime.rs |
| 323 | +++ b/ayllu-build/src/oci_runtime.rs |
| 324 | @@ -76,7 +76,7 @@ impl OciRuntime { |
| 325 | |
| 326 | fn check_output(&self, others: &[&str]) -> Result<std::process::Output, Error> { |
| 327 | let args = self.args(others); |
| 328 | - let output = Command::new(&self.config.oci_binary.clone()) |
| 329 | + let output = Command::new(self.config.oci_binary.clone()) |
| 330 | .args(args.clone()) |
| 331 | .envs(std::env::vars()) |
| 332 | .stdout(Stdio::piped()) |
| 333 | @@ -148,7 +148,7 @@ impl OciRuntime { |
| 334 | let mut cmd = Command::new(&self.config.oci_binary); |
| 335 | cmd.args(args) |
| 336 | .envs(std::env::vars()) |
| 337 | - // .stdin(Stdio::inherit()) |
| 338 | + .stdin(Stdio::null()) |
| 339 | .stdout(Stdio::inherit()) |
| 340 | .stderr(Stdio::inherit()); |
| 341 | |
| 342 | @@ -189,6 +189,7 @@ impl OciRuntime { |
| 343 | let mut proc = Command::new(&self.config.oci_binary) |
| 344 | .args(args.clone()) |
| 345 | .envs(std::env::vars()) |
| 346 | + .stdin(Stdio::null()) |
| 347 | .stdout(Stdio::piped()) |
| 348 | .stderr(Stdio::piped()) |
| 349 | .spawn() |
| 350 | @@ -381,6 +382,7 @@ impl Template { |
| 351 | // TODO: This is an entrypoint for package manager specific tooling |
| 352 | // via composefs and similar. Design is still a WIP. |
| 353 | workflow.cache.iter().for_each(|cache| { |
| 354 | + tracing::info!("Configuring rbind mount: {}: {}", cache.name, cache.path); |
| 355 | mounts.push( |
| 356 | MountBuilder::default() |
| 357 | .source(workspace.mount_path(&cache.name)) |
| 358 | diff --git a/ayllu-build/src/runtime.rs b/ayllu-build/src/runtime.rs |
| 359 | index 257816c..5d9c69e 100644 |
| 360 | --- a/ayllu-build/src/runtime.rs |
| 361 | +++ b/ayllu-build/src/runtime.rs |
| 362 | @@ -1,6 +1,6 @@ |
| 363 | use std::collections::HashMap; |
| 364 | use std::fs::OpenOptions; |
| 365 | - use std::io::Read; |
| 366 | + use std::io::{BufRead, Read}; |
| 367 | use std::ops::Add; |
| 368 | use std::os::unix::fs::MetadataExt; |
| 369 | use std::path::Path; |
| 370 | @@ -79,6 +79,7 @@ fn compute_hash(path: &Path) -> Result<String, Error> { |
| 371 | #[builder(pattern = "owned")] |
| 372 | pub struct Runtime { |
| 373 | config: Config, |
| 374 | + debug: bool, |
| 375 | oci_runtime: OciRuntime, |
| 376 | } |
| 377 | |
| 378 | @@ -161,6 +162,7 @@ impl Runtime { |
| 379 | if let Some(cache) = workflow.cache.as_ref() { |
| 380 | cache.iter().try_for_each(|cache| { |
| 381 | let _ = tx.cache_create( |
| 382 | + manifest_id, |
| 383 | workflow_id, |
| 384 | &cache.name, |
| 385 | &cache.path, |
| 386 | @@ -184,6 +186,10 @@ impl Runtime { |
| 387 | input: &step.input, |
| 388 | workflow_id, |
| 389 | })?; |
| 390 | + step.environment.iter().try_for_each(|(key, value)| { |
| 391 | + let _ = tx.variable_create(step_id, key, value)?; |
| 392 | + Ok::<_, Error>(()) |
| 393 | + })?; |
| 394 | by_name.insert(step.name.clone(), graph.add_node(Unit::Step(step_id))); |
| 395 | let step = tx.step_read(step_id)?; |
| 396 | steps.push(step); |
| 397 | @@ -347,6 +353,23 @@ impl Runtime { |
| 398 | next_step_id, |
| 399 | exit_code |
| 400 | ); |
| 401 | + if self.debug { |
| 402 | + let (_, workspace) = |
| 403 | + active_workflow.as_ref().expect("Workflow is active"); |
| 404 | + let workflow_id = workspace.id().workflow_id; |
| 405 | + eprintln!( |
| 406 | + r#" |
| 407 | + ¡¡¡💣 The Build Has Failed and Debugging Mode is Active 💣!!! |
| 408 | + |
| 409 | + This build has failed and cannot be resumed, however you may still debug this |
| 410 | + session to determine what has gone wrong. |
| 411 | + |
| 412 | + Run `ayllu-build shell {manifest_id} {workflow_id}` to enter into the build environment. |
| 413 | + Once you have finished debugging press enter to continue. |
| 414 | + "# |
| 415 | + ); |
| 416 | + std::io::stdin().lock().read_line(&mut String::default())?; |
| 417 | + } |
| 418 | Self::close_workflow( |
| 419 | &mut conn, |
| 420 | &mut blob, |
| 421 | diff --git a/ayllu-build/src/source.rs b/ayllu-build/src/source.rs |
| 422 | index 9444d8d..fe20aee 100644 |
| 423 | --- a/ayllu-build/src/source.rs |
| 424 | +++ b/ayllu-build/src/source.rs |
| 425 | @@ -38,6 +38,31 @@ pub enum Source { |
| 426 | } |
| 427 | |
| 428 | impl Source { |
| 429 | + pub fn from_arg(arg: Option<&str>) -> Result<Self, Error> { |
| 430 | + match arg { |
| 431 | + Some(path) => { |
| 432 | + let path = Path::new(&path); |
| 433 | + if path.is_file() { |
| 434 | + Ok(Source::Manifest { |
| 435 | + path: path.to_path_buf(), |
| 436 | + }) |
| 437 | + } else { |
| 438 | + let repository = |
| 439 | + ayllu_git::Wrapper::new(path).map_err(Error::CannotResolveHead)?; |
| 440 | + let head = repository.default_ref().map_err(Error::CannotResolveHead)?; |
| 441 | + Ok(Source::Repository { |
| 442 | + path: path.to_path_buf(), |
| 443 | + reference: Some(head), |
| 444 | + }) |
| 445 | + } |
| 446 | + } |
| 447 | + None => Ok(Source::Repository { |
| 448 | + path: std::fs::canonicalize(Path::new("."))?, |
| 449 | + reference: None, |
| 450 | + }), |
| 451 | + } |
| 452 | + } |
| 453 | + |
| 454 | pub fn from_stdin() -> Result<Self, Error> { |
| 455 | let mut line = String::default(); |
| 456 | let _ = std::io::stdin().read_line(&mut line)?; |
| 457 | diff --git a/ayllu-shell/src/main.rs b/ayllu-shell/src/main.rs |
| 458 | index 9bdd2b9..747c3b8 100644 |
| 459 | --- a/ayllu-shell/src/main.rs |
| 460 | +++ b/ayllu-shell/src/main.rs |
| 461 | @@ -10,7 +10,7 @@ fn execute(args: Command) -> Result<(), Box<dyn std::error::Error>> { |
| 462 | let username = args |
| 463 | .username |
| 464 | .unwrap_or(std::env::var("USER").expect("USER is not set")); |
| 465 | - let identity = config |
| 466 | + let _identity = config |
| 467 | .identities |
| 468 | .iter() |
| 469 | .find(|identity| identity.username == username); |
| 470 | diff --git a/ayllu-shell/src/ui/repository.rs b/ayllu-shell/src/ui/repository.rs |
| 471 | index 09674b8..da5ffa0 100644 |
| 472 | --- a/ayllu-shell/src/ui/repository.rs |
| 473 | +++ b/ayllu-shell/src/ui/repository.rs |
| 474 | @@ -1,5 +1,3 @@ |
| 475 | - use std::fmt::Display; |
| 476 | - |
| 477 | use ayllu_git::Sites; |
| 478 | |
| 479 | use crate::ui::prompt::Prompter; |
| 480 | diff --git a/ayllu-web/src/web2/routes/man.rs b/ayllu-web/src/web2/routes/man.rs |
| 481 | index 6ef2ffe..f6656a6 100644 |
| 482 | --- a/ayllu-web/src/web2/routes/man.rs |
| 483 | +++ b/ayllu-web/src/web2/routes/man.rs |
| 484 | @@ -9,7 +9,7 @@ use crate::web2::navigation; |
| 485 | use crate::web2::template::{Base, components}; |
| 486 | |
| 487 | // NOTE: This is circular... so xtask gen_man needs to be run first |
| 488 | - static MAN_PAGES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../target/dist/man"); |
| 489 | + static MAN_PAGES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../dist/man"); |
| 490 | |
| 491 | #[derive(askama::Template)] |
| 492 | #[template(path = "man.html")] |
| 493 | diff --git a/crates/cmd/src/build.rs b/crates/cmd/src/build.rs |
| 494 | index a4b264f..2aca7e2 100644 |
| 495 | --- a/crates/cmd/src/build.rs |
| 496 | +++ b/crates/cmd/src/build.rs |
| 497 | @@ -3,7 +3,12 @@ use std::path::PathBuf; |
| 498 | use clap::{Parser, Subcommand}; |
| 499 | use tracing::Level; |
| 500 | |
| 501 | - const LONG_ABOUT_DESCRIPTION: &str = r#" |
| 502 | + /// Isomorphic Build System |
| 503 | + #[derive(Parser, Debug)] |
| 504 | + #[clap( |
| 505 | + version, |
| 506 | + name = "ayllu-build", |
| 507 | + long_about = r#" |
| 508 | |
| 509 | ayllu-build is an isomorphic continuous integration server that is designed |
| 510 | to minimize the iteration cycle between local and remote builds as much as |
| 511 | @@ -15,16 +20,8 @@ but it should also be compatible with runc or yuoki. |
| 512 | This binary provides a low-level interface to Ayllu builds. Usage via githooks, |
| 513 | ayllu-shell, or quipu is the intended interface for users. |
| 514 | |
| 515 | - The status of builds, their outputs, and logs can be read via quipu(1). |
| 516 | - "#; |
| 517 | - |
| 518 | - /// Isomorphic Build System |
| 519 | - #[derive(Parser, Debug)] |
| 520 | - #[clap( |
| 521 | - version, |
| 522 | - name = "ayllu-build", |
| 523 | - long_about=LONG_ABOUT_DESCRIPTION, |
| 524 | - )] |
| 525 | + The status of builds, their outputs, and logs can be read via quipu(1)."# |
| 526 | + )] |
| 527 | pub struct Command { |
| 528 | /// Path to your configuration file |
| 529 | #[arg(short, long, value_name = "FILE")] |
| 530 | @@ -40,14 +37,31 @@ pub struct Command { |
| 531 | |
| 532 | #[derive(Subcommand, Debug, PartialEq)] |
| 533 | pub enum Commands { |
| 534 | + #[clap(long_about = r#" |
| 535 | + |
| 536 | + Perform as much validation as is possible about the build configuration without |
| 537 | + actually running the build. This will check that any pre-processor returns |
| 538 | + correctly, that the manifest can be parsed by Ayllu, and perform any other |
| 539 | + initialization and validation steps."#)] |
| 540 | + /// Check the build configuration |
| 541 | + Check { |
| 542 | + /// Build Source can be PATH or collection/name |
| 543 | + #[arg(value_name = "SOURCE")] |
| 544 | + source: Option<String>, |
| 545 | + }, |
| 546 | /// Evaluate a local build script |
| 547 | #[clap(long_about = r#" |
| 548 | + |
| 549 | Evaluate a build configuration from the given source. If --stdin is active then |
| 550 | the desired collection/name/[branch] will be read from the input. This will |
| 551 | evaluate each step in the build process until a non-continuable error is reached |
| 552 | or all steps complete successfully. You can use the quipu(1) command to inspect |
| 553 | build artifacts, logs, and other properties. |
| 554 | |
| 555 | + When the --debug flag is enabled anytime a step fails the user will be prompted |
| 556 | + to attach to the failed build in an external terminal and investigate the error. |
| 557 | + Once the user is finished they can press enter and the build will continue. |
| 558 | + |
| 559 | Users have many different desires in terms of configuration file format. |
| 560 | Natively Ayllu will only accept a JSON configuration file for it's build input |
| 561 | but supports the use pre-processors to allow users to specify their build in |
| 562 | @@ -59,9 +73,7 @@ You can configure it in your Ayllu config file as such: |
| 563 | [build.pre_processor] |
| 564 | source = ".ayllu-build.jsonnet" |
| 565 | program = "jsonnet" |
| 566 | - args = ["-"] |
| 567 | - |
| 568 | - "#)] |
| 569 | + args = ["-"] "#)] |
| 570 | Evaluate { |
| 571 | /// If the standard streams should be forwarded to the terminal |
| 572 | #[arg(short, long, action)] |
| 573 | @@ -69,6 +81,9 @@ You can configure it in your Ayllu config file as such: |
| 574 | /// Read the collection / name from stdin |
| 575 | #[arg(short, long, action)] |
| 576 | stdin: bool, |
| 577 | + /// Pause on step failures so you can attach to the environment |
| 578 | + #[arg(short, long, action)] |
| 579 | + debug: bool, |
| 580 | /// Unique build name |
| 581 | #[arg(short, long)] |
| 582 | name: Option<String>, |
| 583 | @@ -79,9 +94,9 @@ You can configure it in your Ayllu config file as such: |
| 584 | // TODO: Also needs a non-interacrtive debugging environment |
| 585 | /// Launch a shell session for debugging purposes |
| 586 | #[clap(long_about = r#" |
| 587 | + |
| 588 | Attach to the container environment of an active build. NOTE that if the build |
| 589 | - process ends you will not be able to enter a shell. |
| 590 | - "#)] |
| 591 | + process ends you will not be able to enter a shell."#)] |
| 592 | Shell { |
| 593 | /// Manifest identifier |
| 594 | manifest_id: i32, |
| 595 | @@ -90,8 +105,8 @@ process ends you will not be able to enter a shell. |
| 596 | }, |
| 597 | /// List active build processes |
| 598 | #[clap(long_about = r#" |
| 599 | - List active processes from runc as reported by the container runtime. |
| 600 | - "#)] |
| 601 | + |
| 602 | + List active processes from runc as reported by the container runtime."#)] |
| 603 | Ps {}, |
| 604 | /// Garbage collect old builds |
| 605 | #[clap(long_about = r#" |
| 606 | @@ -105,8 +120,7 @@ directory under the builds/cache directory. These directories contain OCI |
| 607 | configuration files, build outputs, and other runtime files. |
| 608 | |
| 609 | This will also check for any orphan processes which typically shouldn't exist |
| 610 | - but occasionally may occur. See crun(1), runc(1) or youki(1) for more details. |
| 611 | - "#)] |
| 612 | + but occasionally may occur. See crun(1), runc(1) or youki(1) for more details."#)] |
| 613 | Gc { |
| 614 | /// Number of milliseconds to wait before sending SIGKILL |
| 615 | #[arg(short, long)] |
| 616 | diff --git a/crates/database/src/build.rs b/crates/database/src/build.rs |
| 617 | index 324dd80..a2a5a27 100644 |
| 618 | --- a/crates/database/src/build.rs |
| 619 | +++ b/crates/database/src/build.rs |
| 620 | @@ -706,10 +706,7 @@ pub mod outputs { |
| 621 | } |
| 622 | |
| 623 | pub mod cache { |
| 624 | - use std::{ |
| 625 | - path::Path, |
| 626 | - time::{SystemTime, UNIX_EPOCH}, |
| 627 | - }; |
| 628 | + use std::path::Path; |
| 629 | |
| 630 | use super::*; |
| 631 | use crate::schema::cache::{dsl, table}; |
| 632 | @@ -744,6 +741,7 @@ pub mod cache { |
| 633 | impl Ptr<'_> { |
| 634 | pub fn cache_create( |
| 635 | &mut self, |
| 636 | + manifest_id: i32, |
| 637 | workflow_id: i32, |
| 638 | name: &str, |
| 639 | path: &Path, |
| 640 | @@ -755,9 +753,7 @@ pub mod cache { |
| 641 | // time considerably while allowing you to share artifacts easily |
| 642 | // across workflows. |
| 643 | let name = if transient { |
| 644 | - let now = SystemTime::now(); |
| 645 | - let epoch_secs = now.duration_since(UNIX_EPOCH).unwrap().as_secs(); |
| 646 | - format!("{name}-{epoch_secs}.transient") |
| 647 | + format!("{name}-{manifest_id}.transient") |
| 648 | } else { |
| 649 | name.to_string() |
| 650 | }; |
| 651 | @@ -801,6 +797,22 @@ pub mod variables { |
| 652 | } |
| 653 | |
| 654 | impl Ptr<'_> { |
| 655 | + pub fn variable_create( |
| 656 | + &mut self, |
| 657 | + step_id: i32, |
| 658 | + name: &str, |
| 659 | + value: &str, |
| 660 | + ) -> Result<i32, Error> { |
| 661 | + diesel::insert_into(table) |
| 662 | + .values(( |
| 663 | + dsl::step_id.eq(step_id), |
| 664 | + dsl::name.eq(name), |
| 665 | + dsl::value.eq(value), |
| 666 | + )) |
| 667 | + .returning(dsl::id) |
| 668 | + .get_result(self.0) |
| 669 | + } |
| 670 | + |
| 671 | pub fn variable_read_all(&mut self, step_id: i32) -> Result<Vec<Variable>, Error> { |
| 672 | table |
| 673 | .select(Variable::as_select()) |
| 674 | diff --git a/dist/man/.gitkeep b/dist/man/.gitkeep |
| 675 | new file mode 100644 |
| 676 | index 0000000..e69de29 |
| 677 | --- /dev/null |
| 678 | +++ b/dist/man/.gitkeep |
| 679 | diff --git a/xtask/src/main.rs b/xtask/src/main.rs |
| 680 | index 91b9535..759ee4d 100644 |
| 681 | --- a/xtask/src/main.rs |
| 682 | +++ b/xtask/src/main.rs |
| 683 | @@ -11,8 +11,8 @@ use clap_complete::{ |
| 684 | generate_to, |
| 685 | }; |
| 686 | |
| 687 | - const DIST_COMPLETE_PATH: &str = "target/dist/completion"; |
| 688 | - const DIST_MAN_PATH: &str = "target/dist/man"; |
| 689 | + const DIST_COMPLETE_PATH: &str = "dist/completion"; |
| 690 | + const DIST_MAN_PATH: &str = "dist/man"; |
| 691 | |
| 692 | const AYLLU_SUMMARY: &[u8] = include_bytes!("../../ayllu.7.scd"); |
| 693 | const AYLLU_CONFIG: &str = include_str!("../../ayllu.5.scd"); |