diff --git a/crates/tower-duckdb/src/lib.rs b/crates/tower-duckdb/src/lib.rs index 64754fc7..a96242dc 100644 --- a/crates/tower-duckdb/src/lib.rs +++ b/crates/tower-duckdb/src/lib.rs @@ -1188,12 +1188,15 @@ mod tests { false } - /// MinIO pinned to an immutable release tag rather than `latest`. + /// Coollabs builds official MinIO sources; upstream's prebuilt images are unavailable. + /// https://github.com/coollabsio/minio /// - /// A floating tag lets a new upstream image change this test's behaviour, or - /// break it into a skip, without anyone choosing that. A security regression - /// test should only change when someone means it. Bump deliberately and re-run. - const MINIO_IMAGE_TAG: &str = "RELEASE.2025-09-07T16-13-09Z"; + /// Pin the multi-platform image digest so a rebuilt tag cannot change this security test. + /// Bump deliberately and re-run the object-store test on Linux. + const MINIO_IMAGE_TAG: &str = concat!( + "RELEASE.2025-10-15T17-29-55Z@", + "sha256:69b55a1c1c5dc285ce04db96689f5b2102317fc77a50680a1874ca6efd1c87f9" + ); /// Whether a container runtime that can run this image is reachable. /// @@ -1241,7 +1244,7 @@ mod tests { } let bucket = "warehouse"; - let image = GenericImage::new("quay.io/minio/minio", MINIO_IMAGE_TAG) + let image = GenericImage::new("ghcr.io/coollabsio/minio", MINIO_IMAGE_TAG) .with_wait_for(WaitFor::seconds(1)) .with_exposed_port(9000.tcp()) .with_entrypoint("sh") diff --git a/crates/tower-package/src/core.rs b/crates/tower-package/src/core.rs index 69383c4f..f65233c0 100644 --- a/crates/tower-package/src/core.rs +++ b/crates/tower-package/src/core.rs @@ -74,6 +74,9 @@ pub struct Manifest { pub schedule: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub run_size: Option, + #[serde(default)] pub import_paths: Vec, @@ -108,7 +111,7 @@ pub struct PackageInputs { // module_files have archive_name already rooted under "modules/". pub module_files: Vec, - // towerfile_bytes is the sole source of invoke, parameters, and import_paths. + // towerfile_bytes is the sole source of invoke, parameters, run_size, and import_paths. pub towerfile_bytes: Vec, } @@ -154,6 +157,7 @@ pub fn build_package(inputs: PackageInputs) -> Result { invoke: towerfile.app.script, parameters: towerfile.parameters, schedule: None, + run_size: towerfile.app.run_size, import_paths, app_dir_name: "app".to_string(), modules_dir_name: "modules".to_string(), diff --git a/crates/tower-package/src/native.rs b/crates/tower-package/src/native.rs index 516c8c70..e79c271e 100644 --- a/crates/tower-package/src/native.rs +++ b/crates/tower-package/src/native.rs @@ -20,10 +20,9 @@ use crate::core::{ use crate::error::Error; use crate::towerfile::Towerfile; -// PackageSpec describes how to build a package. Everything core needs (invoke, parameters, -// import_paths as manifest entries) is derived from the Towerfile on disk, so this struct only -// carries what the file resolver needs: where the Towerfile lives, what's considered the project -// root, which globs match app files, and which import paths to walk. +// PackageSpec describes how to build a package. Manifest fields are derived from the Towerfile +// on disk, so this struct only carries what the file resolver needs: where the Towerfile lives, +// what's considered the project root, which globs match app files, and which import paths to walk. #[derive(Debug)] pub struct PackageSpec { pub towerfile_path: PathBuf, @@ -85,6 +84,7 @@ impl Package { invoke: "".to_string(), parameters: vec![], schedule: None, + run_size: None, import_paths: vec![], app_dir_name: "app".to_string(), modules_dir_name: "modules".to_string(), @@ -573,6 +573,7 @@ mod test { invoke: invoke.to_string(), parameters: vec![], schedule: None, + run_size: None, import_paths: vec![], app_dir_name: "app".to_string(), modules_dir_name: "modules".to_string(), diff --git a/crates/tower-package/tests/package_test.rs b/crates/tower-package/tests/package_test.rs index 9aca0b6e..0ef8ef74 100644 --- a/crates/tower-package/tests/package_test.rs +++ b/crates/tower-package/tests/package_test.rs @@ -57,6 +57,11 @@ async fn it_creates_package() { "files {:?} was missing MANIFEST", files ); + + let manifest = &files["MANIFEST"]; + let json: serde_json::Value = serde_json::from_str(manifest).unwrap(); + assert!(json.get("run_size").is_none()); + assert_eq!(Manifest::from_json(manifest).unwrap().run_size, None); } #[tokio::test] @@ -607,9 +612,8 @@ where } #[tokio::test] -async fn it_ships_run_size_in_the_packaged_towerfile() { - // The server reads run_size out of the Towerfile stored on the app version, - // not out of the MANIFEST, so the raw file has to survive packaging verbatim. +async fn it_preserves_run_size_in_the_manifest_and_towerfile() { + // The deploy handler reads run_size from MANIFEST when creating the app version. let tmp_dir = TmpDir::new("example") .await .expect("Failed to create temp dir"); @@ -631,18 +635,13 @@ async fn it_ships_run_size_in_the_packaged_towerfile() { let packaged = files .get("Towerfile") .expect("package was missing the Towerfile entry"); - assert!( - packaged.contains(r#"run_size = "basic.large""#), - "packaged Towerfile lost run_size: {:?}", - packaged - ); + assert_eq!(packaged, towerfile); - // run_size deliberately stays out of the MANIFEST: the runner doesn't act on - // it, and a second copy could disagree with the Towerfile above. let manifest = files.get("MANIFEST").expect("package was missing MANIFEST"); - assert!( - !manifest.contains("run_size"), - "MANIFEST should not carry run_size: {:?}", - manifest + let json: serde_json::Value = serde_json::from_str(manifest).unwrap(); + assert_eq!(json["run_size"], "basic.large"); + assert_eq!( + Manifest::from_json(manifest).unwrap().run_size.as_deref(), + Some("basic.large") ); }