Skip to content

Commit dd58cb9

Browse files
committed
loader: Update to manifest v2
Move spin-trigger UI tests to spin-loader. Signed-off-by: Lann Martin <[email protected]>
1 parent 5e2818b commit dd58cb9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+862
-2090
lines changed

crates/loader/Cargo.toml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,26 @@ reqwest = "0.11.9"
2222
semver = "1.0"
2323
serde = { version = "1.0", features = ["derive"] }
2424
serde_json = "1.0"
25+
sha2 = "0.10.8"
2526
shellexpand = "3.1"
27+
spin-app = { path = "../app" }
2628
spin-common = { path = "../common" }
2729
spin-config = { path = "../config" }
2830
spin-manifest = { path = "../manifest" }
29-
tempfile = "3.3.0"
31+
tempfile = "3.8.0"
3032
terminal = { path = "../terminal" }
33+
thiserror = "1.0.49"
3134
tokio = { version = "1.23", features = [ "full" ] }
3235
tokio-util = "0.6"
33-
toml = "0.5"
36+
toml = "0.8.2"
3437
tracing = { workspace = true }
3538
walkdir = "2.3.2"
3639

37-
[features]
38-
default = ["local"]
39-
local = []
40+
[dev-dependencies]
41+
tokio = { version = "1.23", features = ["rt", "macros"] }
42+
ui-testing = { path = "../ui-testing" }
43+
44+
[[test]]
45+
name = "ui"
46+
path = "tests/ui.rs"
47+
harness = false

crates/loader/src/assets.rs

Lines changed: 0 additions & 92 deletions
This file was deleted.

crates/loader/src/cache.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Cache for OCI registry entities.
22
3-
use anyhow::{bail, Context, Result};
3+
use anyhow::{ensure, Context, Result};
44
use tokio::fs;
55

66
use std::path::{Path, PathBuf};
@@ -12,6 +12,7 @@ const WASM_DIR: &str = "wasm";
1212
const DATA_DIR: &str = "data";
1313

1414
/// Cache for registry entities.
15+
#[derive(Debug)]
1516
pub struct Cache {
1617
/// Root directory for the cache instance.
1718
root: PathBuf,
@@ -49,40 +50,48 @@ impl Cache {
4950

5051
/// Return the path to a wasm file given its digest.
5152
pub fn wasm_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
52-
let path = &self.wasm_dir().join(digest.as_ref());
53-
match path.exists() {
54-
true => Ok(path.into()),
55-
false => bail!(format!(
56-
"cannot find wasm file for digest {}",
57-
digest.as_ref()
58-
)),
59-
}
53+
let path = self.wasm_path(&digest);
54+
ensure!(
55+
path.exists(),
56+
"cannot find wasm file for digest {}",
57+
digest.as_ref()
58+
);
59+
Ok(path)
6060
}
6161

6262
/// Return the path to a data file given its digest.
6363
pub fn data_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
64-
let path = &self.data_dir().join(digest.as_ref());
65-
match path.exists() {
66-
true => Ok(path.into()),
67-
false => bail!(format!(
68-
"cannot find data file for digest {}",
69-
digest.as_ref()
70-
)),
71-
}
64+
let path = self.data_path(&digest);
65+
ensure!(
66+
path.exists(),
67+
"cannot find data file for digest {}",
68+
digest.as_ref()
69+
);
70+
Ok(path)
7271
}
7372

7473
/// Write the contents in the cache's wasm directory.
7574
pub async fn write_wasm(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
76-
fs::write(self.wasm_dir().join(digest.as_ref()), bytes.as_ref()).await?;
75+
fs::write(self.wasm_path(digest), bytes.as_ref()).await?;
7776
Ok(())
7877
}
7978

8079
/// Write the contents in the cache's data directory.
8180
pub async fn write_data(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
82-
fs::write(self.data_dir().join(digest.as_ref()), bytes.as_ref()).await?;
81+
fs::write(self.data_path(digest), bytes.as_ref()).await?;
8382
Ok(())
8483
}
8584

85+
/// The path of contents in the cache's wasm directory, which may or may not exist.
86+
pub fn wasm_path(&self, digest: impl AsRef<str>) -> PathBuf {
87+
self.wasm_dir().join(digest.as_ref())
88+
}
89+
90+
/// The path of contents in the cache's wasm directory, which may or may not exist.
91+
pub fn data_path(&self, digest: impl AsRef<str>) -> PathBuf {
92+
self.data_dir().join(digest.as_ref())
93+
}
94+
8695
/// Ensure the expected configuration directories are found in the root.
8796
/// └── <configuration-root>
8897
/// └── registry

crates/loader/src/common.rs

Lines changed: 0 additions & 33 deletions
This file was deleted.

crates/loader/src/http.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::path::Path;
2+
3+
use anyhow::{ensure, Context, Result};
4+
use sha2::Digest;
5+
use tokio::io::AsyncWriteExt;
6+
7+
/// Download content from `url` while will be verified to match `digest` and
8+
/// then moved to `dest`.
9+
pub async fn verified_download(url: &str, digest: &str, dest: &Path) -> Result<()> {
10+
tracing::debug!("Downloading content from {url:?}");
11+
12+
// Prepare tempfile destination
13+
let prefix = format!("download-{}", digest.replace(':', "-"));
14+
let dest_dir = dest.parent().context("invalid dest")?;
15+
let (temp_file, temp_path) = tempfile::NamedTempFile::with_prefix_in(prefix, dest_dir)
16+
.context("error creating download tempfile")?
17+
.into_parts();
18+
19+
// Begin download
20+
let mut resp = reqwest::get(url).await?.error_for_status()?;
21+
22+
// Hash as we write to the tempfile
23+
let mut hasher = sha2::Sha256::new();
24+
{
25+
let mut temp_file = tokio::fs::File::from_std(temp_file);
26+
while let Some(chunk) = resp.chunk().await? {
27+
hasher.update(&chunk);
28+
temp_file.write_all(&chunk).await?;
29+
}
30+
temp_file.flush().await?;
31+
}
32+
33+
// Check the digest
34+
let actual_digest = format!("sha256:{:x}", hasher.finalize());
35+
ensure!(
36+
actual_digest == digest,
37+
"invalid content digest; expected {digest}, downloaded {actual_digest}"
38+
);
39+
40+
// Move to final destination
41+
temp_path.persist_noclobber(dest)?;
42+
43+
Ok(())
44+
}

crates/loader/src/lib.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,40 @@
1010
1111
#![deny(missing_docs)]
1212

13-
mod assets;
13+
use std::path::{Path, PathBuf};
14+
15+
use anyhow::Result;
16+
use local::LocalLoader;
17+
use spin_app::locked::LockedApp;
18+
use spin_common::paths::parent_dir;
19+
1420
pub mod cache;
15-
mod common;
16-
#[cfg(feature = "local")]
17-
pub mod local;
18-
mod validation;
21+
mod http;
22+
mod local;
1923

20-
/// Load a Spin application configuration from a spin.toml manifest file.
21-
#[cfg(feature = "local")]
22-
pub use local::from_file;
24+
/// Maximum number of files to copy (or download) concurrently
25+
pub(crate) const MAX_FILE_LOADING_CONCURRENCY: usize = 16;
2326

24-
/// Maximum number of assets to process in parallel
25-
pub(crate) const MAX_PARALLEL_ASSET_PROCESSING: usize = 16;
27+
/// Load a Spin locked app from a spin.toml manifest file. If `files_mount_root`
28+
/// is given, `files` mounts will be copied to that directory. If not, `files`
29+
/// mounts will validated as "direct mounts".
30+
pub async fn from_file(
31+
manifest_path: impl AsRef<Path>,
32+
files_mount_strategy: FilesMountStrategy,
33+
) -> Result<LockedApp> {
34+
let path = manifest_path.as_ref();
35+
let app_root = parent_dir(path)?;
36+
let loader = LocalLoader::new(&app_root, files_mount_strategy).await?;
37+
loader.load_file(path).await
38+
}
2639

27-
pub use assets::to_relative;
40+
/// The strategy to use for mounting WASI files into a guest.
41+
#[derive(Debug)]
42+
pub enum FilesMountStrategy {
43+
/// Copy files into the given mount root directory.
44+
Copy(PathBuf),
45+
/// Mount files directly from their source director(ies). This only
46+
/// supports mounting full directories; mounting single files, glob
47+
/// patterns, and `exclude_files` are not supported.
48+
Direct,
49+
}

0 commit comments

Comments
 (0)