Skip to main content

spin_plugins/
store.rs

1use anyhow::Result;
2use flate2::read::GzDecoder;
3use spin_common::data_dir::data_dir;
4use std::{
5    fs::{self, File},
6    path::{Path, PathBuf},
7};
8use tar::Archive;
9
10use crate::{Catalogue, manifest::PluginManifest};
11
12/// Directory where the manifests of installed plugins are stored.
13pub const PLUGIN_MANIFESTS_DIRECTORY_NAME: &str = "manifests";
14const INSTALLATION_RECORD_FILE_NAME: &str = ".install.json";
15
16// Name of directory that contains the cloned centralized Spin plugins
17// repository
18const LOCAL_SNAPSHOT_PATH_IN_STORE: &str = ".spin-plugins";
19
20/// Houses utilities for getting the path to Spin plugin directories.
21pub struct PluginStore {
22    root: PathBuf,
23}
24
25impl PluginStore {
26    pub fn new(root: impl Into<PathBuf>) -> Self {
27        Self { root: root.into() }
28    }
29
30    pub fn try_default() -> Result<Self> {
31        Ok(Self::new(data_dir()?.join("plugins")))
32    }
33
34    /// Gets the path to where Spin plugin are installed.
35    pub fn get_plugins_directory(&self) -> &Path {
36        &self.root
37    }
38
39    /// Get the path to the subdirectory of an installed plugin.
40    pub fn plugin_subdirectory_path(&self, plugin_name: &str) -> PathBuf {
41        self.root.join(plugin_name)
42    }
43
44    pub fn installed_binary_path(&self, plugin_name: &str) -> PathBuf {
45        let mut binary = self.root.join(plugin_name).join(plugin_name);
46        if cfg!(target_os = "windows") {
47            binary.set_extension("exe");
48        }
49        binary
50    }
51
52    /// Get the path to the manifests directory which contains the plugin manifests
53    /// of all installed Spin plugins.
54    pub fn installed_manifests_directory(&self) -> PathBuf {
55        self.root.join(PLUGIN_MANIFESTS_DIRECTORY_NAME)
56    }
57
58    pub fn installed_manifest_path(&self, plugin_name: &str) -> PathBuf {
59        self.installed_manifests_directory()
60            .join(crate::util::manifest_file_name(plugin_name))
61    }
62
63    pub fn installation_record_file(&self, plugin_name: &str) -> PathBuf {
64        self.root
65            .join(plugin_name)
66            .join(INSTALLATION_RECORD_FILE_NAME)
67    }
68
69    pub(crate) fn catalogue(&self) -> Catalogue {
70        let git_root = self.root.join(LOCAL_SNAPSHOT_PATH_IN_STORE);
71        Catalogue::new(git_root)
72    }
73
74    pub(crate) fn add_manifest(&self, plugin_manifest: &PluginManifest) -> Result<()> {
75        let manifests_dir = self.installed_manifests_directory();
76        std::fs::create_dir_all(manifests_dir)?;
77        serde_json::to_writer(
78            &File::create(self.installed_manifest_path(&plugin_manifest.name()))?,
79            plugin_manifest,
80        )?;
81        tracing::trace!("Added manifest for {}", &plugin_manifest.name());
82        Ok(())
83    }
84
85    pub(crate) fn untar_plugin(&self, plugin_file_name: &PathBuf, plugin_name: &str) -> Result<()> {
86        // Get handle to file
87        let tar_gz = File::open(plugin_file_name)?;
88        // Unzip file
89        let tar = GzDecoder::new(tar_gz);
90        // Get plugin from tarball
91        let mut archive = Archive::new(tar);
92        archive.set_preserve_permissions(true);
93        // Create subdirectory in plugins directory for this plugin
94        let plugin_sub_dir = self.plugin_subdirectory_path(plugin_name);
95        fs::remove_dir_all(&plugin_sub_dir).ok();
96        fs::create_dir_all(&plugin_sub_dir)?;
97        archive.unpack(&plugin_sub_dir)?;
98        Ok(())
99    }
100}