spin_loader/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Loaders for Spin applications.
//! This crate implements the possible application sources for Spin applications,
//! and includes functionality to convert the specific configuration (for example
//! local configuration files or from OCI) into Spin configuration that
//! can be consumed by the Spin execution context.
//!
//! This crate can be extended (or replaced entirely) to support additional loaders,
//! and any implementation that produces a `Application` is compatible
//! with the Spin execution context.

#![deny(missing_docs)]

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use local::LocalLoader;
use spin_common::paths::parent_dir;
use spin_locked_app::locked::LockedApp;

pub mod cache;
mod fs;
#[cfg(feature = "async-io")]
mod http;
mod local;

/// Maximum number of files to copy (or download) concurrently
pub(crate) const MAX_FILE_LOADING_CONCURRENCY: usize = 16;

/// Load a Spin locked app from a spin.toml manifest file. If `files_mount_root`
/// is given, `files` mounts will be copied to that directory. If not, `files`
/// mounts will validated as "direct mounts".
pub async fn from_file(
    manifest_path: impl AsRef<Path>,
    files_mount_strategy: FilesMountStrategy,
    cache_root: Option<PathBuf>,
) -> Result<LockedApp> {
    let path = manifest_path.as_ref();
    let app_root = parent_dir(path).context("manifest path has no parent directory")?;
    let loader = LocalLoader::new(&app_root, files_mount_strategy, cache_root).await?;
    loader.load_file(path).await
}

/// Load a Spin locked app from a standalone Wasm file.
pub async fn from_wasm_file(wasm_path: impl AsRef<Path>) -> Result<LockedApp> {
    let app_root = std::env::current_dir()?;
    let manifest = single_file_manifest(wasm_path)?;
    let loader = LocalLoader::new(&app_root, FilesMountStrategy::Direct, None).await?;
    loader.load_manifest(manifest).await
}

/// The strategy to use for mounting WASI files into a guest.
#[derive(Debug)]
pub enum FilesMountStrategy {
    /// Copy files into the given mount root directory.
    Copy(PathBuf),
    /// Mount files directly from their source director(ies). This only
    /// supports mounting full directories; mounting single files, glob
    /// patterns, and `exclude_files` are not supported.
    Direct,
}

fn single_file_manifest(
    wasm_path: impl AsRef<Path>,
) -> anyhow::Result<spin_manifest::schema::v2::AppManifest> {
    use serde::Deserialize;

    let wasm_path_str = wasm_path
        .as_ref()
        .to_str()
        .context("Failed to stringise Wasm file path")?
        .to_owned();
    let app_name = wasm_path
        .as_ref()
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("wasm-file")
        .to_owned();

    let manifest = toml::toml!(
        spin_manifest_version = 2

        [application]
        name = app_name

        [[trigger.http]]
        route = "/..."
        component = { source = wasm_path_str }
    );

    let manifest = spin_manifest::schema::v2::AppManifest::deserialize(manifest)?;

    Ok(manifest)
}