Skip to content

Commit c2c1c8b

Browse files
authored
Merge pull request #2896 from karthik2804/allow_private_plugins
add ability for plugins to be fetched from authenticated URLs
2 parents 3eaba5f + d29ad4c commit c2c1c8b

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

crates/plugins/src/manager.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88

99
use anyhow::{anyhow, bail, Context, Result};
1010
use path_absolutize::Absolutize;
11+
use reqwest::{header::HeaderMap, Client};
1112
use serde::Serialize;
1213
use spin_common::sha256;
1314
use std::{
@@ -87,6 +88,7 @@ impl PluginManager {
8788
plugin_manifest: &PluginManifest,
8889
plugin_package: &PluginPackage,
8990
source: &ManifestLocation,
91+
auth_header_value: &Option<String>,
9092
) -> Result<String> {
9193
let target = plugin_package.url.to_owned();
9294
let target_url = Url::parse(&target)?;
@@ -105,7 +107,15 @@ impl PluginManager {
105107
);
106108
}
107109
}
108-
_ => download_plugin(&plugin_manifest.name(), &temp_dir, &target).await?,
110+
_ => {
111+
download_plugin(
112+
&plugin_manifest.name(),
113+
&temp_dir,
114+
&target,
115+
auth_header_value,
116+
)
117+
.await?
118+
}
109119
};
110120
verify_checksum(&plugin_tarball_path, &plugin_package.sha256)?;
111121

@@ -185,11 +195,16 @@ impl PluginManager {
185195
manifest_location: &ManifestLocation,
186196
skip_compatibility_check: bool,
187197
spin_version: &str,
198+
auth_header_value: &Option<String>,
188199
) -> PluginLookupResult<PluginManifest> {
189200
let plugin_manifest = match manifest_location {
190201
ManifestLocation::Remote(url) => {
191202
tracing::info!("Pulling manifest for plugin from {url}");
192-
reqwest::get(url.as_ref())
203+
let client = Client::new();
204+
client
205+
.get(url.as_ref())
206+
.headers(request_headers(auth_header_value)?)
207+
.send()
193208
.await
194209
.map_err(|e| {
195210
Error::ConnectionFailed(ConnectionFailedError::new(
@@ -334,9 +349,19 @@ pub fn get_package(plugin_manifest: &PluginManifest) -> Result<&PluginPackage> {
334349
})
335350
}
336351

337-
async fn download_plugin(name: &str, temp_dir: &TempDir, target_url: &str) -> Result<PathBuf> {
352+
async fn download_plugin(
353+
name: &str,
354+
temp_dir: &TempDir,
355+
target_url: &str,
356+
auth_header_value: &Option<String>,
357+
) -> Result<PathBuf> {
338358
tracing::trace!("Trying to get tar file for plugin '{name}' from {target_url}");
339-
let plugin_bin = reqwest::get(target_url).await?;
359+
let client = Client::new();
360+
let plugin_bin = client
361+
.get(target_url)
362+
.headers(request_headers(auth_header_value)?)
363+
.send()
364+
.await?;
340365
if !plugin_bin.status().is_success() {
341366
match plugin_bin.status() {
342367
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."),
@@ -364,6 +389,17 @@ fn verify_checksum(plugin_file: &Path, expected_sha256: &str) -> Result<()> {
364389
}
365390
}
366391

392+
/// Get the request headers for a call to the plugin API
393+
///
394+
/// If set, this will include the user provided authorization header.
395+
fn request_headers(auth_header_value: &Option<String>) -> Result<HeaderMap> {
396+
let mut headers = HeaderMap::new();
397+
if let Some(auth_value) = auth_header_value {
398+
headers.insert(reqwest::header::AUTHORIZATION, auth_value.parse()?);
399+
}
400+
Ok(headers)
401+
}
402+
367403
#[cfg(test)]
368404
mod tests {
369405
use super::*;
@@ -385,6 +421,7 @@ mod tests {
385421
&ManifestLocation::Local(PathBuf::from(
386422
"../tests/nonexistent-url/nonexistent-url.json",
387423
)),
424+
&None,
388425
)
389426
.await;
390427

src/commands/external.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ fn installer_for(plugin_name: &str) -> Install {
209209
remote_manifest_src: None,
210210
override_compatibility_check: false,
211211
version: None,
212+
auth_header_value: None,
212213
}
213214
}
214215

src/commands/plugins.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ pub struct Install {
9898
#[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)]
9999
pub override_compatibility_check: bool,
100100

101+
/// Provide the value for the authorization header to be able to install a plugin from a private repository.
102+
/// (e.g) --auth-header-value "Bearer <token>"
103+
#[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)]
104+
pub auth_header_value: Option<String>,
105+
101106
/// Specific version of a plugin to be install from the centralized plugins
102107
/// repository.
103108
#[clap(
@@ -126,6 +131,7 @@ impl Install {
126131
&manifest_location,
127132
self.override_compatibility_check,
128133
SPIN_VERSION,
134+
&self.auth_header_value,
129135
)
130136
.await?;
131137
try_install(
@@ -135,6 +141,7 @@ impl Install {
135141
self.override_compatibility_check,
136142
downgrade,
137143
&manifest_location,
144+
&self.auth_header_value,
138145
)
139146
.await?;
140147
Ok(())
@@ -207,6 +214,11 @@ pub struct Upgrade {
207214
#[clap(short = 'y', long = "yes", takes_value = false)]
208215
pub yes_to_all: bool,
209216

217+
/// Provide the value for the authorization header to be able to install a plugin from a private repository.
218+
/// (e.g) --auth-header-value "Bearer <token>"
219+
#[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)]
220+
pub auth_header_value: Option<String>,
221+
210222
/// Overrides a failed compatibility check of the plugin with the current version of Spin.
211223
#[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)]
212224
pub override_compatibility_check: bool,
@@ -288,7 +300,12 @@ impl Upgrade {
288300

289301
// Attempt to get the manifest to check eligibility to upgrade
290302
if let Ok(manifest) = manager
291-
.get_manifest(&manifest_location, false, SPIN_VERSION)
303+
.get_manifest(
304+
&manifest_location,
305+
false,
306+
SPIN_VERSION,
307+
&self.auth_header_value,
308+
)
292309
.await
293310
{
294311
// Check if upgraded candidates have a newer version and if are compatible
@@ -341,7 +358,16 @@ impl Upgrade {
341358
None,
342359
));
343360

344-
try_install(&manifest, &manager, true, false, false, &manifest_location).await?;
361+
try_install(
362+
&manifest,
363+
&manager,
364+
true,
365+
false,
366+
false,
367+
&manifest_location,
368+
&self.auth_header_value,
369+
)
370+
.await?;
345371
}
346372

347373
Ok(())
@@ -365,6 +391,7 @@ impl Upgrade {
365391
&manifest_location,
366392
self.override_compatibility_check,
367393
SPIN_VERSION,
394+
&self.auth_header_value,
368395
)
369396
.await
370397
{
@@ -382,6 +409,7 @@ impl Upgrade {
382409
self.override_compatibility_check,
383410
self.downgrade,
384411
&manifest_location,
412+
&self.auth_header_value,
385413
)
386414
.await?;
387415
}
@@ -405,6 +433,7 @@ impl Upgrade {
405433
&manifest_location,
406434
self.override_compatibility_check,
407435
SPIN_VERSION,
436+
&self.auth_header_value,
408437
)
409438
.await?;
410439
try_install(
@@ -414,6 +443,7 @@ impl Upgrade {
414443
self.override_compatibility_check,
415444
self.downgrade,
416445
&manifest_location,
446+
&self.auth_header_value,
417447
)
418448
.await?;
419449
Ok(())
@@ -434,6 +464,7 @@ impl Show {
434464
&ManifestLocation::PluginsRepository(PluginLookup::new(&self.name, None)),
435465
false,
436466
SPIN_VERSION,
467+
&None,
437468
)
438469
.await?;
439470

@@ -789,6 +820,7 @@ async fn try_install(
789820
override_compatibility_check: bool,
790821
downgrade: bool,
791822
source: &ManifestLocation,
823+
auth_header_value: &Option<String>,
792824
) -> Result<bool> {
793825
let install_action = manager.check_manifest(
794826
manifest,
@@ -804,7 +836,9 @@ async fn try_install(
804836

805837
let package = manager::get_package(manifest)?;
806838
if continue_to_install(manifest, package, yes_to_all)? {
807-
let installed = manager.install(manifest, package, source).await?;
839+
let installed = manager
840+
.install(manifest, package, source, auth_header_value)
841+
.await?;
808842
println!("Plugin '{installed}' was installed successfully!");
809843

810844
if let Some(description) = manifest.description() {

0 commit comments

Comments
 (0)