wasmcloud_core/
par.rs

1use std::env::consts::{ARCH, OS};
2use std::env::temp_dir;
3use std::path::{Path, PathBuf};
4use std::str;
5
6use anyhow::{anyhow, Context, Result};
7use provider_archive::ProviderArchive;
8use tokio::fs::{self, File, OpenOptions};
9use tokio::io::AsyncWriteExt;
10use wascap::jwt;
11
12fn normalize_for_filename(input: &str) -> String {
13    input
14        .to_lowercase()
15        .replace(|c: char| !c.is_ascii_alphanumeric(), "_")
16}
17
18/// Whether to use the par file cache
19#[derive(Default, Clone, PartialEq, Eq)]
20pub enum UseParFileCache {
21    /// Use the par file cache
22    Ignore,
23    /// Use the par file cache
24    #[default]
25    Use,
26}
27
28fn native_target() -> String {
29    format!("{ARCH}-{OS}")
30}
31
32/// Returns the path to the cache file for a provider
33///
34/// # Arguments
35/// * `host_id` - The host ID this provider is starting on. Required in order to isolate provider caches
36///   for different hosts
37/// * `provider_ref` - The provider reference, e.g. file or OCI
38pub fn cache_path(host_id: impl AsRef<str>, provider_ref: impl AsRef<str>) -> PathBuf {
39    let provider_ref = normalize_for_filename(provider_ref.as_ref());
40
41    let mut cache = temp_dir();
42    cache.push("wasmcloudcache");
43    cache.push(host_id.as_ref());
44    cache.push(&provider_ref);
45    #[cfg(windows)]
46    cache.set_extension("exe");
47    cache
48}
49
50pub(super) async fn create(path: impl AsRef<Path>) -> Result<Option<File>> {
51    let path = path.as_ref();
52    // Check if the file exists and return
53    if fs::metadata(path).await.is_ok() {
54        return Ok(None);
55    }
56    let dir = path.parent().context("failed to determine parent path")?;
57    fs::create_dir_all(dir)
58        .await
59        .context("failed to create parent directory")?;
60
61    open_file(path).await
62}
63
64async fn open_file(path: impl AsRef<Path>) -> Result<Option<File>> {
65    let path = path.as_ref();
66    let mut open_opts = OpenOptions::new();
67    open_opts.create(true).truncate(true).write(true);
68    #[cfg(unix)]
69    open_opts.mode(0o755);
70    open_opts
71        .open(path)
72        .await
73        .map(Some)
74        .with_context(|| format!("failed to open path [{}]", path.display()))
75}
76
77/// Reads a provider archive from the given path and writes it to the cache
78///
79/// # Arguments
80/// * `path` - The path to the provider archive
81/// * `host_id` - The host ID this provider is starting on. Required in order to isolate provider caches
82///   for different hosts
83/// * `provider_ref` - The reference to the provider (e.g. file or OCI). Required to cache provider for future fetches
84pub async fn read(
85    path: impl AsRef<Path>,
86    host_id: impl AsRef<str>,
87    provider_ref: impl AsRef<str>,
88    cache: UseParFileCache,
89) -> Result<(PathBuf, Option<jwt::Token<jwt::CapabilityProvider>>)> {
90    let par = ProviderArchive::try_load_target_from_file(path, &native_target())
91        .await
92        .map_err(|e| anyhow!(e).context("failed to load provider archive"))?;
93    let claims = par.claims_token();
94    let exe = cache_path(host_id, provider_ref);
95
96    let new_file = create(&exe).await?;
97    let mut file = match (cache, new_file) {
98        (UseParFileCache::Use, None) => {
99            return Ok((exe, claims));
100        }
101        (UseParFileCache::Ignore, None) => open_file(&exe)
102            .await?
103            .with_context(|| format!("failed to open file [{}]", exe.display()))?,
104        (UseParFileCache::Use, Some(file)) | (UseParFileCache::Ignore, Some(file)) => file,
105    };
106
107    let target = native_target();
108    let buf = par
109        .target_bytes(&target)
110        .with_context(|| format!("target `{target}` not found"))?;
111    file.write_all(&buf).await.context("failed to write")?;
112    file.flush().await.context("failed to flush")?;
113
114    Ok((exe, claims))
115}