Skip to main content
Glama
lib.rs8.67 kB
use std::path::{ Path, PathBuf, }; use axum::{ Router, http::StatusCode, response::{ IntoResponse, Response, }, routing::post, }; use convert_case::{ Case, Casing, }; use dal::{ ChangeSetError, DalContextBuilder, FuncError, SchemaError, SchemaId, SchemaVariantError, SchemaVariantId, TransactionsError, WorkspaceError, WorkspacePk, WorkspaceSnapshotError, WsEventError, pkg::PkgError as DalPkgError, }; use sdf_core::api_error::ApiError; use si_layer_cache::LayerDbError; use si_pkg::{ SiPkg, SiPkgError, }; use si_std::{ CanonicalFileError, canonical_file::safe_canonically_join, }; use telemetry::prelude::*; use thiserror::Error; use tokio::fs::read_dir; use ulid::Ulid; pub mod approval_process; pub mod import_workspace_vote; pub mod install_module; pub mod upgrade_modules; const PKG_EXTENSION: &str = "sipkg"; const MAX_NAME_SEARCH_ATTEMPTS: usize = 100; #[remain::sorted] #[derive(Error, Debug)] pub enum ModuleError { #[error("Could not canonicalize path: {0}")] Canonicalize(#[from] CanonicalFileError), #[error("change set error: {0}")] ChangeSet(#[from] ChangeSetError), #[error("dal cached module error: {0}")] DalCachedModule(#[from] dal::cached_module::CachedModuleError), #[error("dal pkg error: {0}")] DalPkg(#[from] DalPkgError), #[error("Trying to export from/import into root tenancy")] ExportingImportingWithRootTenancy, #[error("func error: {0}")] Func(#[from] FuncError), #[error("hyper http error: {0}")] Hyper(#[from] hyper::http::Error), #[error("Invalid package file name: {0}")] InvalidPackageFileName(String), #[error("invalid user system init")] InvalidUserSystemInit, #[error("IO Error: {0}")] IoError(#[from] std::io::Error), #[error("LayerDb error: {0}")] LayerDb(#[from] LayerDbError), #[error("dal module error: {0}")] Module(#[from] dal::module::ModuleError), #[error("Module hash not be found: {0}")] ModuleHashNotFound(String), #[error("Module index: {0}")] ModuleIndex(#[from] module_index_client::ModuleIndexClientError), #[error("Module index not configured")] ModuleIndexNotConfigured, #[error("No packages path provided")] NoPackagesPath, #[error("Package with that name already installed: {0}")] PackageAlreadyInstalled(String), #[error("That package already exists: {0}")] PackageAlreadyOnDisk(String), #[error("No schema variants added to package export")] PackageExportEmpty, #[error("Package name required")] PackageNameEmpty, #[error("Package could not be found: {0}")] PackageNotFound(String), #[error("Package version required")] PackageVersionEmpty, #[error("pg error: {0}")] Pg(#[from] si_data_pg::PgError), #[error("pg pool error: {0}")] PgPool(#[from] si_data_pg::PgPoolError), #[error("reqwest error: {0}")] Reqwest(#[from] reqwest::Error), #[error("schema error: {0}")] Schema(#[from] SchemaError), #[error("schema not found for variant {0}")] SchemaNotFoundForVariant(SchemaVariantId), #[error("schema install pkg result empty: {0}")] SchemaNotFoundFromInstall(Ulid), #[error("schema variant error: {0}")] SchemaVariant(#[from] SchemaVariantError), #[error("schema variant not found {0}")] SchemaVariantNotFound(SchemaVariantId), #[error("json serialization error: {0}")] SerdeJson(#[from] serde_json::Error), #[error("si db error: {0}")] SiDb(#[from] si_db::Error), #[error("si pkg error: {0}")] SiPkg(#[from] SiPkgError), #[error("transactions error: {0}")] Transactions(#[from] TransactionsError), #[error("ulid decode error: {0}")] UlidDecode(#[from] ulid::DecodeError), #[error( "found an unlocked schema variant (schema: {1}) for a module to be installed (module: {0})" )] UnlockedSchemaVariantForModuleToInstall(String, SchemaId), #[error("Unable to parse URL: {0}")] Url(#[from] url::ParseError), #[error("workspace error: {0}")] Workspace(#[from] WorkspaceError), #[error("Could not find current workspace {0}")] WorkspaceNotFound(WorkspacePk), #[error("workspace snapshot error: {0}")] WorkspaceSnapshot(#[from] WorkspaceSnapshotError), #[error("could not publish websocket event: {0}")] WsEvent(#[from] WsEventError), } pub type ModuleResult<T> = Result<T, ModuleError>; impl IntoResponse for ModuleError { fn into_response(self) -> Response { let (status_code, error_message) = match self { ModuleError::ModuleHashNotFound(_) | ModuleError::PackageNotFound(_) | ModuleError::SchemaNotFoundForVariant(_) | ModuleError::SchemaVariantNotFound(_) | ModuleError::WorkspaceNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()), }; ApiError::new(status_code, error_message).into_response() } } #[derive(serde::Deserialize, serde::Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PkgView { name: String, installed: bool, hash: Option<String>, } pub async fn get_pkgs_path(builder: &DalContextBuilder) -> ModuleResult<&PathBuf> { match builder.pkgs_path().await { None => Err(ModuleError::NoPackagesPath), Some(path) => Ok(path), } } pub async fn list_pkg_dir_entries(pkgs_path: &Path) -> ModuleResult<Vec<String>> { let mut result = vec![]; let mut entries = read_dir(pkgs_path).await?; while let Some(entry) = entries.next_entry().await? { result.push(entry.file_name().to_string_lossy().to_string()) } Ok(result) } pub async fn pkg_lookup( pkgs_path: &Path, name: &str, ) -> ModuleResult<(Option<PathBuf>, Option<String>)> { let real_pkg_path = safe_canonically_join(pkgs_path, name)?; let file_name = real_pkg_path .file_name() .map(|file_name| file_name.to_string_lossy().to_string()); Ok((real_pkg_path.is_file().then_some(real_pkg_path), file_name)) } fn add_pkg_extension(to: &str, version: &str, attempts: usize) -> String { match attempts { 0 => format!("{to}-{version}.{PKG_EXTENSION}"), more_than_zero => format!("{to}-{version}-{more_than_zero}.{PKG_EXTENSION}"), } } /// Generate a file name automatically based on the package name, appending numbers to the name if /// it conflicts with a file already on disk. pub async fn get_new_pkg_path( builder: &DalContextBuilder, name: &str, version: &str, ) -> ModuleResult<PathBuf> { let name_kebabed = name.to_case(Case::Kebab); let version_kebabed = version.to_case(Case::Kebab); let mut attempts = 0; loop { let file_name = add_pkg_extension(&name_kebabed, &version_kebabed, attempts); let real_pkg_path = match Path::new(&file_name).file_name() { None => return Err(ModuleError::InvalidPackageFileName(file_name)), Some(file_name) => Path::join(get_pkgs_path(builder).await?, file_name), }; if attempts > MAX_NAME_SEARCH_ATTEMPTS { return Err(ModuleError::PackageAlreadyOnDisk( real_pkg_path.to_string_lossy().to_string(), )); } else if real_pkg_path.is_file() { attempts = 1; continue; } return Ok(real_pkg_path); } } pub async fn pkg_open(builder: &DalContextBuilder, file_name: &str) -> ModuleResult<SiPkg> { let pkg_tuple = pkg_lookup(get_pkgs_path(builder).await?, file_name).await?; let real_pkg_path = match pkg_tuple { (None, _) => return Err(ModuleError::PackageNotFound(file_name.to_string())), (Some(real_pkg_path), _) => real_pkg_path, }; Ok(SiPkg::load_from_file(&real_pkg_path).await?) } pub fn routes() -> Router<sdf_core::app_state::AppState> { Router::new() .route("/install_module", post(install_module::install_module)) // USED IN CUSTOMIZE SCREEN .route("/upgrade_modules", post(upgrade_modules::upgrade_modules)) // USED IN CUSTOMIZE SCREEN .route( "/begin_approval_process", // USED IN CUSTOMIZE SCREEN post(approval_process::begin_approval_process), ) .route( "/cancel_approval_process", // USED IN CUSTOMIZE SCREEN post(approval_process::cancel_approval_process), ) .route( "/import_workspace_vote", // USED IN CUSTOMIZE SCREEN post(import_workspace_vote::import_workspace_vote), ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server