Commit

Author:

Hash:

Timestamp:

+94 -39 +/-4 browse

Kevin Schoon [me@kevinschoon.com]

cc91cc3c7d9cd5eb36b77cbdc373b46212cd08fe

Fri, 15 May 2026 08:31:02 +0000 (3 weeks ago)

improve logging support for ayllu-build
improve logging support for ayllu-build

You may now specify --forward to pass container stdout/stderr directly
to your terminal. If --logged is specified then the container
stdout/stderr is structued via the system logger.
1diff --git a/ayllu-build/src/libpod.rs b/ayllu-build/src/libpod.rs
2index fef0d1f..a1cd647 100644
3--- a/ayllu-build/src/libpod.rs
4+++ b/ayllu-build/src/libpod.rs
5 @@ -30,6 +30,15 @@ pub enum Stream {
6 Stderr,
7 }
8
9+ impl From<ayllu_database::build::logs::Stream> for Stream {
10+ fn from(value: ayllu_database::build::logs::Stream) -> Self {
11+ match value {
12+ ayllu_database::build::logs::Stream::Stdout => Stream::Stdout,
13+ ayllu_database::build::logs::Stream::Stderr => Stream::Stderr,
14+ }
15+ }
16+ }
17+
18 pub mod container {
19
20 use super::*;
21 @@ -324,7 +333,7 @@ impl Client {
22 ctx: &Context,
23 shell: &str,
24 input: &str,
25- ch: Sender<(Stream, String)>,
26+ ch: Sender<(Stream, Vec<u8>)>,
27 signal: tokio::sync::oneshot::Sender<i32>,
28 ) -> Result<(), Error> {
29 let name = format!("ayllu-build-{}-{}", ctx.manifest_id, ctx.workflow_id);
30 @@ -347,20 +356,21 @@ impl Client {
31 match reader.read_exact(&mut header).await {
32 Ok(_) => {
33 let (stream, size) = Header::read(&header);
34- tracing::debug!("read stream: {stream:?} {size}");
35+ tracing::trace!("read stream: {stream:?} {size}");
36 let mut buf = vec![0; size as usize];
37- reader.read_exact(buf.as_mut_slice()).await.unwrap();
38- for line in buf.split(|&b| b == b'\n') {
39- let line = if line.last() == Some(&b'\r') {
40- &line[..line.len() - 1]
41- } else {
42- line
43- };
44- if !line.is_empty() {
45- let line = String::from_utf8_lossy(line);
46- ch.send((stream, line.to_string())).await.unwrap()
47- }
48- }
49+ reader.read_exact(buf.as_mut_slice()).await?;
50+ ch.send((stream, buf)).await.unwrap();
51+ // for line in buf.split(|&b| b == b'\n') {
52+ // let line = if line.last() == Some(&b'\r') {
53+ // &line[..line.len() - 1]
54+ // } else {
55+ // line
56+ // };
57+ // if !line.is_empty() {
58+ // let line = String::from_utf8_lossy(line);
59+ // ch.send((stream, line.to_string())).await.unwrap()
60+ // }
61+ // }
62 }
63 Err(e) => match e.kind() {
64 tokio::io::ErrorKind::UnexpectedEof => break,
65 diff --git a/ayllu-build/src/libpod_executor.rs b/ayllu-build/src/libpod_executor.rs
66index 4bb6d46..317ab9a 100644
67--- a/ayllu-build/src/libpod_executor.rs
68+++ b/ayllu-build/src/libpod_executor.rs
69 @@ -1,3 +1,4 @@
70+ use std::io::{BufRead, Write};
71 use std::time::Duration;
72 use std::{collections::HashMap, path::Path};
73
74 @@ -6,6 +7,7 @@ use tokio::sync::{mpsc, oneshot};
75 use ayllu_database::build::logs::{Stream, WriteLineArgs};
76 use ayllu_database::build::steps::Step;
77 use serde::Deserialize;
78+ use tracing::Level;
79
80 use crate::libpod::container::HealthConfig;
81 use crate::libpod::{
82 @@ -39,12 +41,20 @@ pub struct InitializeArgs<'a> {
83
84 pub struct Libpod {
85 client: Client,
86+ level: tracing::Level,
87+ forward: bool,
88 }
89
90 impl Libpod {
91- pub fn new(socket_path: &Path) -> Self {
92+ pub fn new(socket_path: &Path, forward: bool, use_logger: bool) -> Self {
93 Self {
94 client: Client::new(socket_path),
95+ level: if use_logger {
96+ tracing::Level::INFO
97+ } else {
98+ tracing::Level::TRACE
99+ },
100+ forward,
101 }
102 }
103 }
104 @@ -128,7 +138,7 @@ impl Libpod {
105 context: &Context,
106 db: &mut ayllu_database::Ptr<'a>,
107 ) -> Result<i32, crate::error::Error> {
108- let (tx, mut rx) = mpsc::channel::<(crate::libpod::Stream, String)>(1);
109+ let (tx, mut rx) = mpsc::channel::<(crate::libpod::Stream, Vec<u8>)>(1);
110 let (tx_signal, rx_signal) = oneshot::channel::<i32>();
111 let shell = step.shell.to_string();
112 let input = step.input.to_string();
113 @@ -140,17 +150,39 @@ impl Libpod {
114 }
115 });
116 while let Some(msg) = rx.recv().await {
117- let stream = match msg.0 {
118- crate::libpod::Stream::Stdin => unimplemented!(),
119- crate::libpod::Stream::Stdout => Stream::Stdout,
120- crate::libpod::Stream::Stderr => Stream::Stderr,
121+ let (stream, chunk) = match &msg {
122+ (crate::libpod::Stream::Stdin, _) => unimplemented!(),
123+ (crate::libpod::Stream::Stdout, chunk) => (Stream::Stdout, chunk),
124+ (crate::libpod::Stream::Stderr, chunk) => (Stream::Stderr, chunk),
125 };
126- tracing::trace!(stream = stream.to_string(), message = msg.1.trim_end());
127- db.log_write_line(&WriteLineArgs {
128- step_id: step.id,
129- stream,
130- line: &msg.1,
131- })?;
132+ if self.forward {
133+ match stream {
134+ Stream::Stdout => std::io::stdout().write_all(chunk).unwrap(),
135+ Stream::Stderr => std::io::stderr().write_all(chunk).unwrap(),
136+ }
137+ };
138+ let mut buf = std::io::Cursor::new(chunk);
139+ 'reader: loop {
140+ let mut line = String::default();
141+ if let Ok(n) = buf.read_line(&mut line) {
142+ let line = line.trim_end_matches("\n");
143+ if matches!(self.level, Level::INFO) {
144+ tracing::info!(stream = format!("{}", stream), "{}", line);
145+ } else {
146+ tracing::trace!(stream = format!("{}", stream), "{}", line);
147+ };
148+ db.log_write_line(&WriteLineArgs {
149+ step_id: step.id,
150+ stream,
151+ line,
152+ })?;
153+ if n == 0 {
154+ break 'reader;
155+ }
156+ } else {
157+ break 'reader;
158+ }
159+ }
160 }
161
162 let signal = rx_signal.await.unwrap();
163 @@ -160,7 +192,6 @@ impl Libpod {
164 tracing::warn!("Non-zero exit code: {signal}")
165 }
166
167- // self.client.call().await.unwrap();
168 Ok(signal)
169 }
170
171 diff --git a/ayllu-build/src/main.rs b/ayllu-build/src/main.rs
172index 33ec53d..2598196 100644
173--- a/ayllu-build/src/main.rs
174+++ b/ayllu-build/src/main.rs
175 @@ -5,7 +5,7 @@ use tracing::Level;
176 use ayllu_cmd::build::{Command, Commands};
177 use ayllu_database::Wrapper as Database;
178
179- use crate::{config::Config, evaluate::Runtime, source::Source};
180+ use crate::{evaluate::Runtime, source::Source};
181
182 mod config;
183 mod error;
184 @@ -25,6 +25,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
185 source,
186 stdin,
187 name,
188+ logging,
189 } => {
190 let source = if stdin {
191 tracing::info!("Reading source from stdin for build {name:?}");
192 @@ -50,7 +51,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
193 work_dir: workdir.clone(),
194 init_path: cfg.builder.init_binary.clone(),
195 };
196- let executor = libpod_executor::Libpod::new(&cfg.builder.podman_socket);
197+ let executor = libpod_executor::Libpod::new(
198+ &cfg.builder.podman_socket,
199+ logging.forward,
200+ logging.logged,
201+ );
202 rt.evaluate(&cfg, &executor).await?;
203 Ok(())
204 }
205 diff --git a/crates/cmd/src/build.rs b/crates/cmd/src/build.rs
206index 4eb74b8..d4360c1 100644
207--- a/crates/cmd/src/build.rs
208+++ b/crates/cmd/src/build.rs
209 @@ -2,19 +2,24 @@ use std::path::PathBuf;
210
211 use clap::{Parser, Subcommand};
212 use tracing::Level;
213- use url::Url;
214
215 const LONG_ABOUT_DESCRIPTION: &str = r#"
216
217 ayllu-build provides a continuous integration server that works both locally
218 and on a remote server.
219+
220+ When --logged is enabled log output is displayed through the standard logger
221+ along with the corresponding step, workflow id, and stream type.
222+
223+ When --forward is enabled the stdout/stderr of the step process is forwarded
224+ directly to your terminal.
225 "#;
226
227 #[derive(Parser, Debug)]
228 #[clap(
229 version,
230 about,
231- name = "ayllu-shell",
232+ name = "ayllu-build",
233 long_about=LONG_ABOUT_DESCRIPTION,
234 )]
235 pub struct Command {
236 @@ -30,20 +35,24 @@ pub struct Command {
237 pub command: Commands,
238 }
239
240- pub enum Source {
241- LocalPath(PathBuf),
242- RemoteUrl(Url),
243- }
244-
245- pub enum Environment {
246- Local,
247- Libpod,
248+ #[derive(clap::Args, Debug, PartialEq)]
249+ #[group(required = false, multiple = false)]
250+ pub struct Logging {
251+ /// step stream is logged via the internal logger
252+ #[clap(short, long, action)]
253+ pub logged: bool,
254+ /// step stream will be forwarded directly to the terminal
255+ #[clap(short, long, action)]
256+ pub forward: bool,
257 }
258
259 #[derive(Subcommand, Debug, PartialEq)]
260 pub enum Commands {
261 /// evaluate a local build script
262 Evaluate {
263+ /// Configures build logging
264+ #[clap(flatten)]
265+ logging: Logging,
266 /// Read the collection / name from stdin
267 #[arg(short, long, action)]
268 stdin: bool,