Skip to content

Commit

Permalink
Merge pull request #2896 from karthik2804/allow_private_plugins
Browse files Browse the repository at this point in the history
add ability for plugins to be fetched from authenticated URLs
  • Loading branch information
itowlson authored Oct 29, 2024
2 parents 3eaba5f + d29ad4c commit c2c1c8b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 7 deletions.
45 changes: 41 additions & 4 deletions crates/plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{

use anyhow::{anyhow, bail, Context, Result};
use path_absolutize::Absolutize;
use reqwest::{header::HeaderMap, Client};
use serde::Serialize;
use spin_common::sha256;
use std::{
Expand Down Expand Up @@ -87,6 +88,7 @@ impl PluginManager {
plugin_manifest: &PluginManifest,
plugin_package: &PluginPackage,
source: &ManifestLocation,
auth_header_value: &Option<String>,
) -> Result<String> {
let target = plugin_package.url.to_owned();
let target_url = Url::parse(&target)?;
Expand All @@ -105,7 +107,15 @@ impl PluginManager {
);
}
}
_ => download_plugin(&plugin_manifest.name(), &temp_dir, &target).await?,
_ => {
download_plugin(
&plugin_manifest.name(),
&temp_dir,
&target,
auth_header_value,
)
.await?
}
};
verify_checksum(&plugin_tarball_path, &plugin_package.sha256)?;

Expand Down Expand Up @@ -185,11 +195,16 @@ impl PluginManager {
manifest_location: &ManifestLocation,
skip_compatibility_check: bool,
spin_version: &str,
auth_header_value: &Option<String>,
) -> PluginLookupResult<PluginManifest> {
let plugin_manifest = match manifest_location {
ManifestLocation::Remote(url) => {
tracing::info!("Pulling manifest for plugin from {url}");
reqwest::get(url.as_ref())
let client = Client::new();
client
.get(url.as_ref())
.headers(request_headers(auth_header_value)?)
.send()
.await
.map_err(|e| {
Error::ConnectionFailed(ConnectionFailedError::new(
Expand Down Expand Up @@ -334,9 +349,19 @@ pub fn get_package(plugin_manifest: &PluginManifest) -> Result<&PluginPackage> {
})
}

async fn download_plugin(name: &str, temp_dir: &TempDir, target_url: &str) -> Result<PathBuf> {
async fn download_plugin(
name: &str,
temp_dir: &TempDir,
target_url: &str,
auth_header_value: &Option<String>,
) -> Result<PathBuf> {
tracing::trace!("Trying to get tar file for plugin '{name}' from {target_url}");
let plugin_bin = reqwest::get(target_url).await?;
let client = Client::new();
let plugin_bin = client
.get(target_url)
.headers(request_headers(auth_header_value)?)
.send()
.await?;
if !plugin_bin.status().is_success() {
match plugin_bin.status() {
reqwest::StatusCode::NOT_FOUND => bail!("The download URL specified in the plugin manifest was not found ({target_url} returned HTTP error 404). Please contact the plugin author."),
Expand Down Expand Up @@ -364,6 +389,17 @@ fn verify_checksum(plugin_file: &Path, expected_sha256: &str) -> Result<()> {
}
}

/// Get the request headers for a call to the plugin API
///
/// If set, this will include the user provided authorization header.
fn request_headers(auth_header_value: &Option<String>) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
if let Some(auth_value) = auth_header_value {
headers.insert(reqwest::header::AUTHORIZATION, auth_value.parse()?);
}
Ok(headers)
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -385,6 +421,7 @@ mod tests {
&ManifestLocation::Local(PathBuf::from(
"../tests/nonexistent-url/nonexistent-url.json",
)),
&None,
)
.await;

Expand Down
1 change: 1 addition & 0 deletions src/commands/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ fn installer_for(plugin_name: &str) -> Install {
remote_manifest_src: None,
override_compatibility_check: false,
version: None,
auth_header_value: None,
}
}

Expand Down
40 changes: 37 additions & 3 deletions src/commands/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ pub struct Install {
#[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)]
pub override_compatibility_check: bool,

/// Provide the value for the authorization header to be able to install a plugin from a private repository.
/// (e.g) --auth-header-value "Bearer <token>"
#[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)]
pub auth_header_value: Option<String>,

/// Specific version of a plugin to be install from the centralized plugins
/// repository.
#[clap(
Expand Down Expand Up @@ -126,6 +131,7 @@ impl Install {
&manifest_location,
self.override_compatibility_check,
SPIN_VERSION,
&self.auth_header_value,
)
.await?;
try_install(
Expand All @@ -135,6 +141,7 @@ impl Install {
self.override_compatibility_check,
downgrade,
&manifest_location,
&self.auth_header_value,
)
.await?;
Ok(())
Expand Down Expand Up @@ -207,6 +214,11 @@ pub struct Upgrade {
#[clap(short = 'y', long = "yes", takes_value = false)]
pub yes_to_all: bool,

/// Provide the value for the authorization header to be able to install a plugin from a private repository.
/// (e.g) --auth-header-value "Bearer <token>"
#[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)]
pub auth_header_value: Option<String>,

/// Overrides a failed compatibility check of the plugin with the current version of Spin.
#[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)]
pub override_compatibility_check: bool,
Expand Down Expand Up @@ -288,7 +300,12 @@ impl Upgrade {

// Attempt to get the manifest to check eligibility to upgrade
if let Ok(manifest) = manager
.get_manifest(&manifest_location, false, SPIN_VERSION)
.get_manifest(
&manifest_location,
false,
SPIN_VERSION,
&self.auth_header_value,
)
.await
{
// Check if upgraded candidates have a newer version and if are compatible
Expand Down Expand Up @@ -341,7 +358,16 @@ impl Upgrade {
None,
));

try_install(&manifest, &manager, true, false, false, &manifest_location).await?;
try_install(
&manifest,
&manager,
true,
false,
false,
&manifest_location,
&self.auth_header_value,
)
.await?;
}

Ok(())
Expand All @@ -365,6 +391,7 @@ impl Upgrade {
&manifest_location,
self.override_compatibility_check,
SPIN_VERSION,
&self.auth_header_value,
)
.await
{
Expand All @@ -382,6 +409,7 @@ impl Upgrade {
self.override_compatibility_check,
self.downgrade,
&manifest_location,
&self.auth_header_value,
)
.await?;
}
Expand All @@ -405,6 +433,7 @@ impl Upgrade {
&manifest_location,
self.override_compatibility_check,
SPIN_VERSION,
&self.auth_header_value,
)
.await?;
try_install(
Expand All @@ -414,6 +443,7 @@ impl Upgrade {
self.override_compatibility_check,
self.downgrade,
&manifest_location,
&self.auth_header_value,
)
.await?;
Ok(())
Expand All @@ -434,6 +464,7 @@ impl Show {
&ManifestLocation::PluginsRepository(PluginLookup::new(&self.name, None)),
false,
SPIN_VERSION,
&None,
)
.await?;

Expand Down Expand Up @@ -789,6 +820,7 @@ async fn try_install(
override_compatibility_check: bool,
downgrade: bool,
source: &ManifestLocation,
auth_header_value: &Option<String>,
) -> Result<bool> {
let install_action = manager.check_manifest(
manifest,
Expand All @@ -804,7 +836,9 @@ async fn try_install(

let package = manager::get_package(manifest)?;
if continue_to_install(manifest, package, yes_to_all)? {
let installed = manager.install(manifest, package, source).await?;
let installed = manager
.install(manifest, package, source, auth_header_value)
.await?;
println!("Plugin '{installed}' was installed successfully!");

if let Some(description) = manifest.description() {
Expand Down

0 comments on commit c2c1c8b

Please sign in to comment.