mod build;
mod checksum;
mod source;
use crate::shapes::*;
use derive_setters::Setters;
use rustc_hash::FxHashMap;
use schematic::Schema;
use std::path::PathBuf;
use version_spec::{CalVer, SemVer, SpecError, UnresolvedVersionSpec, VersionSpec};
use warpgate_api::*;
pub use build::*;
pub use checksum::*;
pub use semver::{Version, VersionReq};
pub use source::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PluginFunction {
RegisterTool,
DefineToolConfig,
RegisterBackend,
DefineBackendConfig,
DetectVersionFiles,
ParseVersionFile,
PinVersion,
UnpinVersion,
LoadVersions,
ResolveVersion,
DownloadPrebuilt,
BuildInstructions,
UnpackArchive,
VerifyChecksum,
NativeInstall,
NativeUninstall,
LocateExecutables,
SyncManifest,
SyncShellProfile,
ActivateEnvironment,
}
impl PluginFunction {
pub fn as_str(&self) -> &'static str {
match self {
Self::RegisterTool => "register_tool",
Self::DefineToolConfig => "define_tool_config",
Self::RegisterBackend => "register_backend",
Self::DefineBackendConfig => "define_backend_config",
Self::DetectVersionFiles => "detect_version_files",
Self::ParseVersionFile => "parse_version_file",
Self::PinVersion => "pin_version",
Self::UnpinVersion => "unpin_version",
Self::LoadVersions => "load_versions",
Self::ResolveVersion => "resolve_version",
Self::DownloadPrebuilt => "download_prebuilt",
Self::BuildInstructions => "build_instructions",
Self::UnpackArchive => "unpack_archive",
Self::VerifyChecksum => "verify_checksum",
Self::NativeInstall => "native_install",
Self::NativeUninstall => "native_uninstall",
Self::LocateExecutables => "locate_executables",
Self::SyncManifest => "sync_manifest",
Self::SyncShellProfile => "sync_shell_profile",
Self::ActivateEnvironment => "activate_environment",
}
}
}
impl AsRef<str> for PluginFunction {
fn as_ref(&self) -> &str {
self.as_str()
}
}
pub(crate) fn is_false(value: &bool) -> bool {
!(*value)
}
pub(crate) fn is_default<T: Default + PartialEq>(value: &T) -> bool {
value == &T::default()
}
api_struct!(
pub struct PluginContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proto_version: Option<Version>,
pub temp_dir: VirtualPath,
pub tool_dir: VirtualPath,
pub version: VersionSpec,
pub working_dir: VirtualPath,
}
);
api_struct!(
pub struct PluginUnresolvedContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proto_version: Option<Version>,
pub temp_dir: VirtualPath,
#[doc(hidden)]
#[deprecated]
pub tool_dir: VirtualPath,
#[doc(hidden)]
#[deprecated]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<VersionSpec>,
pub working_dir: VirtualPath,
}
);
api_unit_enum!(
pub enum PluginType {
#[serde(alias = "CLI", alias = "CommandLine")] CommandLine,
#[default]
#[serde(alias = "Language")]
Language,
#[serde(alias = "PM", alias = "DependencyManager")] DependencyManager,
#[serde(alias = "VM", alias = "VersionManager")] VersionManager,
}
);
api_struct!(
pub struct RegisterToolInput {
pub id: Id,
}
);
#[deprecated(note = "Use `RegisterToolInput` instead.")]
pub type ToolMetadataInput = RegisterToolInput;
api_struct!(
#[serde(default)]
pub struct ToolInventoryOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub override_dir: Option<VirtualPath>,
#[serde(skip_serializing_if = "is_false")]
pub scoped_backend_dir: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub version_suffix: Option<String>,
}
);
api_unit_enum!(
pub enum InstallStrategy {
#[serde(alias = "BuildFromSource")]
BuildFromSource,
#[default]
#[serde(alias = "DownloadPrebuilt")]
DownloadPrebuilt,
}
);
api_struct!(
#[serde(default)]
pub struct ToolLockOptions {
#[serde(skip_serializing_if = "is_false")]
pub ignore_os_arch: bool,
#[serde(skip_serializing_if = "is_false")]
pub no_record: bool,
}
);
api_struct!(
pub struct RegisterToolOutput {
#[serde(default)]
pub default_install_strategy: InstallStrategy,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_version: Option<UnresolvedVersionSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deprecations: Vec<String>,
#[serde(default, skip_serializing_if = "is_default", alias = "inventory")]
pub inventory_options: ToolInventoryOptions,
#[serde(default, skip_serializing_if = "is_default")]
pub lock_options: ToolLockOptions,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minimum_proto_version: Option<Version>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub plugin_version: Option<Version>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub requires: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub self_upgrade_commands: Vec<String>,
#[serde(rename = "type")]
pub type_of: PluginType,
#[serde(default)]
pub unstable: Switch,
}
);
#[deprecated(note = "Use `RegisterToolOutput` instead.")]
pub type ToolMetadataOutput = RegisterToolOutput;
pub type ConfigSchema = Schema;
api_struct!(
pub struct DefineToolConfigOutput {
pub schema: ConfigSchema,
}
);
api_struct!(
pub struct RegisterBackendInput {
pub context: PluginUnresolvedContext,
pub id: Id,
}
);
api_struct!(
pub struct RegisterBackendOutput {
pub backend_id: Id,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exes: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<SourceLocation>,
}
);
api_struct!(
pub struct DefineBackendConfigOutput {
pub schema: ConfigSchema,
}
);
api_struct!(
pub struct DetectVersionInput {
pub context: PluginUnresolvedContext,
}
);
api_struct!(
#[serde(default)]
pub struct DetectVersionOutput {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub files: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ignore: Vec<String>,
}
);
api_struct!(
pub struct ParseVersionFileInput {
pub content: String,
pub context: PluginUnresolvedContext,
pub file: String,
pub path: VirtualPath,
}
);
api_struct!(
#[serde(default)]
pub struct ParseVersionFileOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<UnresolvedVersionSpec>,
}
);
api_struct!(
pub struct PinVersionInput {
pub context: PluginUnresolvedContext,
pub dir: VirtualPath,
pub version: UnresolvedVersionSpec,
}
);
api_struct!(
#[serde(default)]
pub struct PinVersionOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<VirtualPath>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub pinned: bool,
}
);
api_struct!(
pub struct UnpinVersionInput {
pub context: PluginUnresolvedContext,
pub dir: VirtualPath,
}
);
api_struct!(
#[serde(default)]
pub struct UnpinVersionOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<VirtualPath>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub unpinned: bool,
pub version: Option<UnresolvedVersionSpec>,
}
);
api_struct!(
pub struct NativeInstallInput {
pub context: PluginContext,
pub force: bool,
pub install_dir: VirtualPath,
}
);
api_struct!(
pub struct NativeInstallOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum: Option<Checksum>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub installed: bool,
#[serde(default)]
pub skip_install: bool,
}
);
api_struct!(
pub struct NativeUninstallInput {
pub context: PluginContext,
pub uninstall_dir: VirtualPath,
}
);
api_struct!(
pub struct NativeUninstallOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub uninstalled: bool,
#[serde(default)]
pub skip_uninstall: bool,
}
);
api_struct!(
pub struct DownloadPrebuiltInput {
pub context: PluginContext,
pub install_dir: VirtualPath,
}
);
api_struct!(
pub struct DownloadPrebuiltOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub archive_prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum: Option<Checksum>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_public_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub download_name: Option<String>,
pub download_url: String,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
pub http_headers: FxHashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub post_script: Option<PathBuf>,
}
);
api_struct!(
pub struct UnpackArchiveInput {
pub context: PluginContext,
pub input_file: VirtualPath,
pub output_dir: VirtualPath,
}
);
api_struct!(
pub struct VerifyChecksumInput {
pub context: PluginContext,
pub checksum_file: VirtualPath,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub download_checksum: Option<Checksum>,
pub download_file: VirtualPath,
}
);
api_struct!(
pub struct VerifyChecksumOutput {
pub verified: bool,
}
);
api_struct!(
pub struct LocateExecutablesInput {
pub context: PluginContext,
pub install_dir: VirtualPath,
}
);
api_struct!(
#[derive(Setters)]
#[serde(default)]
pub struct ExecutableConfig {
#[setters(strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub exe_path: Option<PathBuf>,
#[setters(strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub exe_link_path: Option<PathBuf>,
#[serde(skip_serializing_if = "is_false")]
pub no_bin: bool,
#[serde(skip_serializing_if = "is_false")]
pub no_shim: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub parent_exe_args: Vec<String>,
#[setters(into, strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_exe_name: Option<String>,
#[serde(skip_serializing_if = "is_false")]
pub primary: bool,
#[setters(strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub shim_before_args: Option<StringOrVec>,
#[setters(strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub shim_after_args: Option<StringOrVec>,
#[setters(strip_option)]
#[serde(skip_serializing_if = "Option::is_none")]
pub shim_env_vars: Option<FxHashMap<String, String>>,
#[serde(skip_serializing_if = "is_false")]
pub update_perms: bool,
}
);
impl ExecutableConfig {
pub fn new<T: AsRef<str>>(exe_path: T) -> Self {
Self {
exe_path: Some(PathBuf::from(exe_path.as_ref())),
..ExecutableConfig::default()
}
}
pub fn new_primary<T: AsRef<str>>(exe_path: T) -> Self {
Self {
exe_path: Some(PathBuf::from(exe_path.as_ref())),
primary: true,
..ExecutableConfig::default()
}
}
pub fn with_parent<T: AsRef<str>, P: AsRef<str>>(exe_path: T, parent_exe: P) -> Self {
Self {
exe_path: Some(PathBuf::from(exe_path.as_ref())),
parent_exe_name: Some(parent_exe.as_ref().to_owned()),
..ExecutableConfig::default()
}
}
}
api_struct!(
#[serde(default)]
pub struct LocateExecutablesOutput {
#[serde(skip_serializing_if = "FxHashMap::is_empty")]
pub exes: FxHashMap<String, ExecutableConfig>,
#[deprecated(note = "Use `exes_dirs` instead.")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exes_dir: Option<PathBuf>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub exes_dirs: Vec<PathBuf>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub globals_lookup_dirs: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub globals_prefix: Option<String>,
}
);
api_struct!(
pub struct ActivateEnvironmentInput {
pub context: PluginContext,
pub globals_dir: Option<VirtualPath>,
}
);
api_struct!(
#[serde(default)]
pub struct ActivateEnvironmentOutput {
pub env: FxHashMap<String, String>,
pub paths: Vec<PathBuf>,
}
);
api_struct!(
pub struct LoadVersionsInput {
pub context: PluginUnresolvedContext,
pub initial: UnresolvedVersionSpec,
}
);
api_struct!(
#[serde(default)]
pub struct LoadVersionsOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub canary: Option<UnresolvedVersionSpec>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latest: Option<UnresolvedVersionSpec>,
#[serde(skip_serializing_if = "FxHashMap::is_empty")]
pub aliases: FxHashMap<String, UnresolvedVersionSpec>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub versions: Vec<VersionSpec>,
}
);
impl LoadVersionsOutput {
pub fn from(values: Vec<String>) -> Result<Self, SpecError> {
let mut versions = vec![];
for value in values {
versions.push(VersionSpec::parse(&value)?);
}
Ok(Self::from_versions(versions))
}
pub fn from_versions(versions: Vec<VersionSpec>) -> Self {
let mut output = LoadVersionsOutput::default();
let mut latest = Version::new(0, 0, 0);
let mut calver = false;
for version in versions {
if let Some(inner) = version.as_version() {
if inner.pre.is_empty() && inner.build.is_empty() && inner > &latest {
inner.clone_into(&mut latest);
calver = matches!(version, VersionSpec::Calendar(_));
}
}
output.versions.push(version);
}
output.latest = Some(if calver {
UnresolvedVersionSpec::Calendar(CalVer(latest))
} else {
UnresolvedVersionSpec::Semantic(SemVer(latest))
});
output
.aliases
.insert("latest".into(), output.latest.clone().unwrap());
output
}
}
api_struct!(
pub struct ResolveVersionInput {
pub context: PluginUnresolvedContext,
pub initial: UnresolvedVersionSpec,
}
);
api_struct!(
#[serde(default)]
pub struct ResolveVersionOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate: Option<UnresolvedVersionSpec>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<VersionSpec>,
}
);
api_struct!(
pub struct SyncManifestInput {
pub context: PluginUnresolvedContext,
}
);
api_struct!(
#[serde(default)]
pub struct SyncManifestOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub versions: Option<Vec<VersionSpec>>,
pub skip_sync: bool,
}
);
api_struct!(
pub struct SyncShellProfileInput {
pub context: PluginContext,
pub passthrough_args: Vec<String>,
}
);
api_struct!(
pub struct SyncShellProfileOutput {
pub check_var: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub export_vars: Option<FxHashMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extend_path: Option<Vec<String>>,
#[serde(default)]
pub skip_sync: bool,
}
);