Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,524 changes: 1,405 additions & 119 deletions ATTRIBUTIONS-Rust.md

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ clap_complete = "4"
console = "0.16"
futures-util = "0.3"
fs2 = "0.4"
flate2 = "1"
http = "1"
http-body-util = "0.1"
hyper = { version = "1.11.1", features = ["client", "server", "http1", "http2"] }
Expand All @@ -63,6 +64,7 @@ serde_yaml = "0.9"
sha2 = "0.11"
strum = { version = "0.27", features = ["derive"] }
subtle = "2"
tar = "0.4"
thiserror = "2"
tokio = { version = "1", features = ["io-std", "io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
Expand All @@ -71,6 +73,7 @@ toml = "0.9"
toml_edit = "0.23"
uuid = { workspace = true, features = ["serde", "v7"] }
zstd = "0.13"
zip = { version = "2", default-features = false, features = ["deflate"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"
Expand Down
21 changes: 19 additions & 2 deletions crates/cli/src/commands/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,20 @@ pub(super) fn execute(command: PluginsCommand, server: &ServerArgs) -> Result<Ex
subcommands::PluginsSubcommand::Add(command) => {
crate::plugins::lifecycle::add(command.into_runtime(), &server)
}
subcommands::PluginsSubcommand::Install(command) => {
crate::plugins::lifecycle::installation::install(
command.source,
command.scope.into(),
command.no_enable,
&server,
)
}
subcommands::PluginsSubcommand::Validate(command) => {
crate::plugins::lifecycle::validate(command.into_runtime(), &server)
}
subcommands::PluginsSubcommand::List(command) => {
crate::plugins::lifecycle::list(command.into_runtime(), &server)
let scope = command.scope.clone().into();
crate::plugins::lifecycle::list_scoped(command.into_runtime(), scope, &server)
}
subcommands::PluginsSubcommand::Inspect(command) => {
crate::plugins::lifecycle::inspect(command.into_runtime(), &server)
Expand All @@ -55,7 +64,15 @@ pub(super) fn execute(command: PluginsCommand, server: &ServerArgs) -> Result<Ex
crate::plugins::lifecycle::disable(command.into_runtime(), &server)
}
subcommands::PluginsSubcommand::Remove(command) => {
crate::plugins::lifecycle::remove(command.into_runtime(), &server)
let scope = command.scope.clone().into();
crate::plugins::lifecycle::remove_scoped(command.into_runtime(), scope, &server)
}
subcommands::PluginsSubcommand::Uninstall(command) => {
crate::plugins::lifecycle::installation::uninstall(
command.id,
command.scope.into(),
&server,
)
}
};
match result {
Expand Down
31 changes: 29 additions & 2 deletions crates/cli/src/commands/plugins/subcommands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub(crate) enum PluginsSubcommand {
Edit(PluginsEditCommand),
/// Register a manifest-backed dynamic plugin in `plugins.toml`.
Add(PluginsAddCommand),
/// Install a verified plugin bundle from a release source.
Install(PluginsInstallCommand),
/// Validate a manifest-backed dynamic plugin by path or installed ID.
Validate(PluginsValidateCommand),
/// List discovered dynamic plugins from the resolved host config.
Expand All @@ -42,6 +44,8 @@ pub(crate) enum PluginsSubcommand {
Disable(PluginsDisableCommand),
/// Tombstone a registered dynamic plugin and remove its host discovery reference.
Remove(PluginsRemoveCommand),
/// Unregister and delete a CLI-managed plugin bundle.
Uninstall(PluginsUninstallCommand),
}

impl PluginsSubcommand {
Expand Down Expand Up @@ -72,10 +76,10 @@ impl PluginsSubcommand {
.multiple(false)
))]
pub(crate) struct PluginsScopeArgs {
/// Edit the selected low layer: an explicit plugin target, or the XDG user config.
/// Select plugins in your user configuration (XDG unless an explicit target applies).
#[arg(long)]
pub(crate) user: bool,
/// Edit system config (`/etc/nemo-relay` on Unix; `%ProgramData%\nemo-relay` on Windows).
/// Select system plugins (`/etc/nemo-relay` on Unix; `%ProgramData%\nemo-relay` on Windows).
#[arg(long)]
pub(crate) global: bool,
}
Expand All @@ -96,6 +100,17 @@ pub(crate) struct PluginsAddCommand {
pub(crate) path: PathBuf,
}

#[derive(Debug, Clone, Args)]
pub(crate) struct PluginsInstallCommand {
#[command(flatten)]
pub(crate) scope: PluginsScopeArgs,
/// Release source, for example github:NVIDIA/NeMo-Relay-Plugins@switchyard-plugin-0.2.0.
pub(crate) source: String,
/// Register the plugin without enabling it.
#[arg(long)]
pub(crate) no_enable: bool,
}

/// Args for `nemo-relay plugins validate`.
#[derive(Debug, Clone, Args)]
pub(crate) struct PluginsValidateCommand {
Expand All @@ -109,6 +124,8 @@ pub(crate) struct PluginsValidateCommand {
/// Args for `nemo-relay plugins list`.
#[derive(Debug, Clone, Default, Args)]
pub(crate) struct PluginsListCommand {
#[command(flatten)]
pub(crate) scope: PluginsScopeArgs,
/// Include tombstoned dynamic plugin records in the output.
#[arg(long)]
pub(crate) all: bool,
Expand Down Expand Up @@ -144,6 +161,16 @@ pub(crate) struct PluginsDisableCommand {
/// Args for `nemo-relay plugins remove`.
#[derive(Debug, Clone, Args)]
pub(crate) struct PluginsRemoveCommand {
#[command(flatten)]
pub(crate) scope: PluginsScopeArgs,
/// Canonical plugin ID.
pub(crate) id: String,
}

#[derive(Debug, Clone, Args)]
pub(crate) struct PluginsUninstallCommand {
#[command(flatten)]
pub(crate) scope: PluginsScopeArgs,
/// Canonical plugin ID.
pub(crate) id: String,
}
Expand Down
62 changes: 62 additions & 0 deletions crates/cli/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,54 @@ pub(crate) fn resolve_plugins_config_with_path(
Ok(resolved)
}

/// Resolve only the selected plugin configuration files for scoped CLI diagnostics.
pub(crate) fn resolve_plugin_config_from_paths(
paths: impl IntoIterator<Item = PathBuf>,
) -> Result<ResolvedConfig, CliError> {
let plugin_toml = load_plugin_toml_config_from_paths(paths)?;
let mut resolved = ResolvedConfig::default();
apply_plugin_toml_config(&mut resolved, plugin_toml);
Ok(resolved)
}

/// Read the effective host policy without loading manifests from other scopes.
pub(crate) fn resolve_dynamic_plugin_policy_from_paths(
paths: impl IntoIterator<Item = PathBuf>,
) -> Result<DynamicPluginHostPolicy, CliError> {
let mut policy = DynamicPluginHostPolicy::default();
for path in deduplicate_plugin_config_paths(paths) {
let Some(raw) = read_config_file(&path, false, "plugin configuration")? else {
continue;
};
let parsed = raw.parse::<toml::Table>().map_err(|error| {
CliError::Config(format!(
"invalid plugin TOML in {}: {error}",
path.display()
))
})?;
let Some(plugins) = parsed.get("plugins") else {
continue;
};
let plugins = plugins.as_table().ok_or_else(|| {
CliError::Config(format!(
"invalid plugin TOML in {}: [plugins] must be a table",
path.display()
))
})?;
if let Some(value) = plugins.get("policy") {
let file_policy: crate::plugins::policy::FileDynamicPluginHostPolicy =
value.clone().try_into().map_err(|error| {
CliError::Config(format!(
"invalid dynamic plugin policy in {}: {error}",
path.display()
))
})?;
policy.merge_from(file_policy.into());
}
}
Ok(policy)
}

/// Resolves transparent `run` configuration and switches the gateway to an ephemeral bind address.
///
/// Explicit run arguments override inherited top-level server flags, which override shared config.
Expand Down Expand Up @@ -1526,9 +1574,23 @@ pub(crate) fn user_config_dir() -> Option<PathBuf> {

/// Resolves the platform system config directory shared with the core plugin runtime.
pub(crate) fn system_config_dir() -> PathBuf {
#[cfg(test)]
if let Some(path) = TEST_SYSTEM_CONFIG_DIR.with(|value| value.borrow().clone()) {
return path;
}
nemo_relay::plugin::system_config_dir()
}

#[cfg(test)]
thread_local! {
static TEST_SYSTEM_CONFIG_DIR: std::cell::RefCell<Option<PathBuf>> = const { std::cell::RefCell::new(None) };
}

#[cfg(test)]
pub(crate) fn set_test_system_config_dir(path: Option<PathBuf>) -> Option<PathBuf> {
TEST_SYSTEM_CONFIG_DIR.with(|value| value.replace(path))
}

// Applies the typed TOML config model to the resolved runtime config. Missing sections and fields
// are ignored, preserving defaults and prior merge layers.
fn apply_file_config(resolved: &mut ResolvedConfig, value: toml::Value) -> Result<(), CliError> {
Expand Down
Loading
Loading