feat: --no-cache build flag and Sol build target

- Add --no-cache flag to sunbeam build (passes --no-cache to buildctl)
- Add Sol (virtual librarian) as a build target
- Wire no_cache through all build functions and dispatch
This commit is contained in:
2026-03-20 21:31:42 +00:00
parent f75f61f238
commit 8d6e815a91
2 changed files with 82 additions and 42 deletions

View File

@@ -88,6 +88,9 @@ pub enum Verb {
/// Apply manifests and rollout restart after pushing (implies --push). /// Apply manifests and rollout restart after pushing (implies --push).
#[arg(long)] #[arg(long)]
deploy: bool, deploy: bool,
/// Disable buildkitd layer cache.
#[arg(long)]
no_cache: bool,
}, },
/// Functional service health checks. /// Functional service health checks.
@@ -243,6 +246,7 @@ pub enum BuildTarget {
Tuwunel, Tuwunel,
Calendars, Calendars,
Projects, Projects,
Sol,
} }
impl std::fmt::Display for BuildTarget { impl std::fmt::Display for BuildTarget {
@@ -265,6 +269,7 @@ impl std::fmt::Display for BuildTarget {
BuildTarget::Tuwunel => "tuwunel", BuildTarget::Tuwunel => "tuwunel",
BuildTarget::Calendars => "calendars", BuildTarget::Calendars => "calendars",
BuildTarget::Projects => "projects", BuildTarget::Projects => "projects",
BuildTarget::Sol => "sol",
}; };
write!(f, "{s}") write!(f, "{s}")
} }
@@ -465,10 +470,11 @@ mod tests {
fn test_build_proxy() { fn test_build_proxy() {
let cli = parse(&["sunbeam", "build", "proxy"]); let cli = parse(&["sunbeam", "build", "proxy"]);
match cli.verb { match cli.verb {
Some(Verb::Build { what, push, deploy }) => { Some(Verb::Build { what, push, deploy, no_cache }) => {
assert!(matches!(what, BuildTarget::Proxy)); assert!(matches!(what, BuildTarget::Proxy));
assert!(!push); assert!(!push);
assert!(!deploy); assert!(!deploy);
assert!(!no_cache);
} }
_ => panic!("expected Build"), _ => panic!("expected Build"),
} }
@@ -479,10 +485,11 @@ mod tests {
fn test_build_deploy_flag() { fn test_build_deploy_flag() {
let cli = parse(&["sunbeam", "build", "proxy", "--deploy"]); let cli = parse(&["sunbeam", "build", "proxy", "--deploy"]);
match cli.verb { match cli.verb {
Some(Verb::Build { deploy, push, .. }) => { Some(Verb::Build { deploy, push, no_cache, .. }) => {
assert!(deploy); assert!(deploy);
// clap does not imply --push; that logic is in dispatch() // clap does not imply --push; that logic is in dispatch()
assert!(!push); assert!(!push);
assert!(!no_cache);
} }
_ => panic!("expected Build"), _ => panic!("expected Build"),
} }
@@ -838,9 +845,9 @@ pub async fn dispatch() -> Result<()> {
crate::services::cmd_restart(target.as_deref()).await crate::services::cmd_restart(target.as_deref()).await
} }
Some(Verb::Build { what, push, deploy }) => { Some(Verb::Build { what, push, deploy, no_cache }) => {
let push = push || deploy; let push = push || deploy;
crate::images::cmd_build(&what, push, deploy).await crate::images::cmd_build(&what, push, deploy, no_cache).await
} }
Some(Verb::Check { target }) => { Some(Verb::Check { target }) => {

View File

@@ -837,7 +837,7 @@ async fn clear_image_pull_error_pods() -> Result<()> {
// Per-service build functions // Per-service build functions
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async fn build_proxy(push: bool, deploy: bool) -> Result<()> { async fn build_proxy(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let proxy_dir = crate::config::get_repo_root().join("proxy"); let proxy_dir = crate::config::get_repo_root().join("proxy");
if !proxy_dir.is_dir() { if !proxy_dir.is_dir() {
@@ -855,7 +855,7 @@ async fn build_proxy(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -866,7 +866,7 @@ async fn build_proxy(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_tuwunel(push: bool, deploy: bool) -> Result<()> { async fn build_tuwunel(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let tuwunel_dir = crate::config::get_repo_root().join("tuwunel"); let tuwunel_dir = crate::config::get_repo_root().join("tuwunel");
if !tuwunel_dir.is_dir() { if !tuwunel_dir.is_dir() {
@@ -884,7 +884,7 @@ async fn build_tuwunel(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -895,7 +895,7 @@ async fn build_tuwunel(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_integration(push: bool, deploy: bool) -> Result<()> { async fn build_integration(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let sunbeam_dir = crate::config::get_repo_root(); let sunbeam_dir = crate::config::get_repo_root();
let integration_service_dir = sunbeam_dir.join("integration-service"); let integration_service_dir = sunbeam_dir.join("integration-service");
@@ -940,7 +940,7 @@ async fn build_integration(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await; .await;
@@ -957,7 +957,7 @@ async fn build_integration(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_kratos_admin(push: bool, deploy: bool) -> Result<()> { async fn build_kratos_admin(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let kratos_admin_dir = crate::config::get_repo_root().join("kratos-admin"); let kratos_admin_dir = crate::config::get_repo_root().join("kratos-admin");
if !kratos_admin_dir.is_dir() { if !kratos_admin_dir.is_dir() {
@@ -978,7 +978,7 @@ async fn build_kratos_admin(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -989,7 +989,7 @@ async fn build_kratos_admin(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_meet(push: bool, deploy: bool) -> Result<()> { async fn build_meet(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let meet_dir = crate::config::get_repo_root().join("meet"); let meet_dir = crate::config::get_repo_root().join("meet");
if !meet_dir.is_dir() { if !meet_dir.is_dir() {
@@ -1009,7 +1009,7 @@ async fn build_meet(push: bool, deploy: bool) -> Result<()> {
Some("backend-production"), Some("backend-production"),
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1035,7 +1035,7 @@ async fn build_meet(push: bool, deploy: bool) -> Result<()> {
Some("frontend-production"), Some("frontend-production"),
Some(&build_args), Some(&build_args),
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1053,7 +1053,7 @@ async fn build_meet(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_people(push: bool, deploy: bool) -> Result<()> { async fn build_people(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let people_dir = crate::config::get_repo_root().join("people"); let people_dir = crate::config::get_repo_root().join("people");
if !people_dir.is_dir() { if !people_dir.is_dir() {
@@ -1109,7 +1109,7 @@ async fn build_people(push: bool, deploy: bool) -> Result<()> {
Some("frontend-production"), Some("frontend-production"),
Some(&build_args), Some(&build_args),
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1160,7 +1160,7 @@ const MESSAGES_COMPONENTS: &[(&str, &str, &str, Option<&str>)] = &[
), ),
]; ];
async fn build_messages(what: &str, push: bool, deploy: bool) -> Result<()> { async fn build_messages(what: &str, push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let messages_dir = crate::config::get_repo_root().join("messages"); let messages_dir = crate::config::get_repo_root().join("messages");
if !messages_dir.is_dir() { if !messages_dir.is_dir() {
@@ -1214,7 +1214,7 @@ async fn build_messages(what: &str, push: bool, deploy: bool) -> Result<()> {
*target, *target,
None, None,
push, push,
false, no_cache,
&cleanup_paths, &cleanup_paths,
) )
.await?; .await?;
@@ -1257,6 +1257,7 @@ async fn build_la_suite_frontend(
namespace: &str, namespace: &str,
push: bool, push: bool,
deploy: bool, deploy: bool,
no_cache: bool,
) -> Result<()> { ) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
@@ -1307,7 +1308,7 @@ async fn build_la_suite_frontend(
Some("frontend-production"), Some("frontend-production"),
Some(&build_args), Some(&build_args),
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1439,7 +1440,7 @@ async fn patch_dockerfile_uv(
Ok((patched_df, cleanup)) Ok((patched_df, cleanup))
} }
async fn build_projects(push: bool, deploy: bool) -> Result<()> { async fn build_projects(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let projects_dir = crate::config::get_repo_root().join("projects"); let projects_dir = crate::config::get_repo_root().join("projects");
if !projects_dir.is_dir() { if !projects_dir.is_dir() {
@@ -1457,7 +1458,7 @@ async fn build_projects(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1468,7 +1469,36 @@ async fn build_projects(push: bool, deploy: bool) -> Result<()> {
Ok(()) Ok(())
} }
async fn build_calendars(push: bool, deploy: bool) -> Result<()> { async fn build_sol(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?;
let sol_dir = crate::config::get_repo_root().join("sol");
if !sol_dir.is_dir() {
return Err(SunbeamError::build(format!("Sol source not found at {}", sol_dir.display())));
}
let image = format!("{}/studio/sol:latest", env.registry);
step(&format!("Building sol -> {image} ..."));
build_image(
&env,
&image,
&sol_dir.join("Dockerfile"),
&sol_dir,
None,
None,
push,
no_cache,
&[],
)
.await?;
if deploy {
deploy_rollout(&env, &["sol"], "matrix", 120, None).await?;
}
Ok(())
}
async fn build_calendars(push: bool, deploy: bool, no_cache: bool) -> Result<()> {
let env = get_build_env().await?; let env = get_build_env().await?;
let cal_dir = crate::config::get_repo_root().join("calendars"); let cal_dir = crate::config::get_repo_root().join("calendars");
if !cal_dir.is_dir() { if !cal_dir.is_dir() {
@@ -1518,7 +1548,7 @@ async fn build_calendars(push: bool, deploy: bool) -> Result<()> {
Some("backend-production"), Some("backend-production"),
None, None,
push, push,
false, no_cache,
&cleanup, &cleanup,
) )
.await?; .await?;
@@ -1535,7 +1565,7 @@ async fn build_calendars(push: bool, deploy: bool) -> Result<()> {
None, None,
None, None,
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1573,7 +1603,7 @@ async fn build_calendars(push: bool, deploy: bool) -> Result<()> {
Some("frontend-production"), Some("frontend-production"),
Some(&build_args), Some(&build_args),
push, push,
false, no_cache,
&[], &[],
) )
.await?; .await?;
@@ -1601,12 +1631,12 @@ async fn build_calendars(push: bool, deploy: bool) -> Result<()> {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/// Build an image. Pass push=true to push, deploy=true to also apply + rollout. /// Build an image. Pass push=true to push, deploy=true to also apply + rollout.
pub async fn cmd_build(what: &BuildTarget, push: bool, deploy: bool) -> Result<()> { pub async fn cmd_build(what: &BuildTarget, push: bool, deploy: bool, no_cache: bool) -> Result<()> {
match what { match what {
BuildTarget::Proxy => build_proxy(push, deploy).await, BuildTarget::Proxy => build_proxy(push, deploy, no_cache).await,
BuildTarget::Integration => build_integration(push, deploy).await, BuildTarget::Integration => build_integration(push, deploy, no_cache).await,
BuildTarget::KratosAdmin => build_kratos_admin(push, deploy).await, BuildTarget::KratosAdmin => build_kratos_admin(push, deploy, no_cache).await,
BuildTarget::Meet => build_meet(push, deploy).await, BuildTarget::Meet => build_meet(push, deploy, no_cache).await,
BuildTarget::DocsFrontend => { BuildTarget::DocsFrontend => {
let repo_dir = crate::config::get_repo_root().join("docs"); let repo_dir = crate::config::get_repo_root().join("docs");
build_la_suite_frontend( build_la_suite_frontend(
@@ -1620,22 +1650,24 @@ pub async fn cmd_build(what: &BuildTarget, push: bool, deploy: bool) -> Result<(
"lasuite", "lasuite",
push, push,
deploy, deploy,
no_cache,
) )
.await .await
} }
BuildTarget::PeopleFrontend | BuildTarget::People => build_people(push, deploy).await, BuildTarget::PeopleFrontend | BuildTarget::People => build_people(push, deploy, no_cache).await,
BuildTarget::Messages => build_messages("messages", push, deploy).await, BuildTarget::Messages => build_messages("messages", push, deploy, no_cache).await,
BuildTarget::MessagesBackend => build_messages("messages-backend", push, deploy).await, BuildTarget::MessagesBackend => build_messages("messages-backend", push, deploy, no_cache).await,
BuildTarget::MessagesFrontend => build_messages("messages-frontend", push, deploy).await, BuildTarget::MessagesFrontend => build_messages("messages-frontend", push, deploy, no_cache).await,
BuildTarget::MessagesMtaIn => build_messages("messages-mta-in", push, deploy).await, BuildTarget::MessagesMtaIn => build_messages("messages-mta-in", push, deploy, no_cache).await,
BuildTarget::MessagesMtaOut => build_messages("messages-mta-out", push, deploy).await, BuildTarget::MessagesMtaOut => build_messages("messages-mta-out", push, deploy, no_cache).await,
BuildTarget::MessagesMpa => build_messages("messages-mpa", push, deploy).await, BuildTarget::MessagesMpa => build_messages("messages-mpa", push, deploy, no_cache).await,
BuildTarget::MessagesSocksProxy => { BuildTarget::MessagesSocksProxy => {
build_messages("messages-socks-proxy", push, deploy).await build_messages("messages-socks-proxy", push, deploy, no_cache).await
} }
BuildTarget::Tuwunel => build_tuwunel(push, deploy).await, BuildTarget::Tuwunel => build_tuwunel(push, deploy, no_cache).await,
BuildTarget::Calendars => build_calendars(push, deploy).await, BuildTarget::Calendars => build_calendars(push, deploy, no_cache).await,
BuildTarget::Projects => build_projects(push, deploy).await, BuildTarget::Projects => build_projects(push, deploy, no_cache).await,
BuildTarget::Sol => build_sol(push, deploy, no_cache).await,
} }
} }
@@ -1730,6 +1762,7 @@ mod tests {
BuildTarget::Tuwunel, BuildTarget::Tuwunel,
BuildTarget::Calendars, BuildTarget::Calendars,
BuildTarget::Projects, BuildTarget::Projects,
BuildTarget::Sol,
]; ];
for t in &targets { for t in &targets {
let s = t.to_string(); let s = t.to_string();