Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handling of plugin entrypoint string #626

Merged
merged 1 commit into from
Nov 14, 2024
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
24 changes: 15 additions & 9 deletions hipcheck/src/plugin/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::{
hc_error,
hipcheck::plugin_service_client::PluginServiceClient,
plugin::{HcPluginClient, Plugin, PluginContext},
plugin::{try_get_bin_for_entrypoint, HcPluginClient, Plugin, PluginContext},
Result,
};
use futures::future::join_all;
Expand Down Expand Up @@ -75,22 +75,28 @@ impl PluginExecutor {
// on the cmdline is not already in use, but it is still possible for that
// port to become unavailable between our check and the plugin's bind attempt.
// Hence the need for subsequent attempts if we get unlucky

log::debug!("Starting plugin '{}'", plugin.name);

// `entrypoint` is a string that represents a CLI invocation which may contain
// arguments, so we have to split off only the first token
if let Some(bin_path) = try_get_bin_for_entrypoint(&plugin.entrypoint).0 {
if which::which(bin_path).is_err() {
log::warn!(
"Binary '{}' used to spawn {} does not exist, spawn is unlikely to succeed",
bin_path,
plugin.name
);
}
}

let mut spawn_attempts: usize = 0;
while spawn_attempts < self.max_spawn_attempts {
// Find free port for process. Don't retry if we fail since this means all
// ports in the desired range are already bound
let port = self.get_available_port()?;
let port_str = port.to_string();

if let Ok(false) = std::fs::exists(&plugin.entrypoint) {
log::warn!(
"entrypoint '{}' for {} does not exist",
plugin.entrypoint,
plugin.name
)
}

// Spawn plugin process
log::debug!("Spawning '{}' on port {}", &plugin.entrypoint, port_str);
let Ok(mut proc) = Command::new(&plugin.entrypoint)
Expand Down
4 changes: 3 additions & 1 deletion hipcheck/src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::error::Result;
pub use crate::plugin::{get_plugin_key, manager::*, plugin_id::PluginId, types::*};
pub use arch::{get_current_arch, try_set_arch, Arch};
pub use download_manifest::{ArchiveFormat, DownloadManifest, HashAlgorithm, HashWithDigest};
pub use plugin_manifest::{PluginManifest, PluginName, PluginPublisher, PluginVersion};
pub use plugin_manifest::{
try_get_bin_for_entrypoint, PluginManifest, PluginName, PluginPublisher, PluginVersion,
};
pub use retrieval::retrieve_plugins;
use serde_json::Value;
use std::collections::HashMap;
Expand Down
34 changes: 29 additions & 5 deletions hipcheck/src/plugin/plugin_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
use kdl::{KdlDocument, KdlNode};
use std::{
collections::HashMap,
ops::Not,
path::{Path, PathBuf},
str::FromStr,
};
Expand Down Expand Up @@ -247,20 +248,32 @@ impl PluginManifest {
Self::from_str(read_string(path)?.as_str())
}

pub fn get_entrypoint_for(&self, arch: &Arch) -> Result<String, Error> {
self.entrypoints
.0
.get(arch)
.map(String::from)
.ok_or(hc_error!("No entrypoint for current arch ({})", arch))
}

/// Update the directory that an entrypoint is stored in
///
/// Returns previous entrypoint
pub fn update_entrypoint<P>(&mut self, arch: &Arch, new_directory: P) -> Result<PathBuf, Error>
where
P: AsRef<Path>,
{
let current_entrypoint = self
let curr_entrypoint_str = self
.entrypoints
.0
.remove(arch)
.map(PathBuf::from)
.ok_or(hc_error!("No entrypoint for current arch ({})", arch))?;

let (opt_bin_path, args) = try_get_bin_for_entrypoint(&curr_entrypoint_str);
let bin_path = opt_bin_path
.map(PathBuf::from)
.ok_or(hc_error!("Malformed entrypoint string"))?;

fn new_path(new_directory: &Path, current_entrypoint: &Path) -> Result<PathBuf, Error> {
let entrypoint_filename =
new_directory.join(current_entrypoint.file_name().ok_or(hc_error!(
Expand All @@ -270,9 +283,15 @@ impl PluginManifest {
Ok(entrypoint_filename)
}

let new_entrypoint = new_path(new_directory.as_ref(), &current_entrypoint)?;
self.set_entrypoint(arch.clone(), new_entrypoint.to_string_lossy().to_string());
Ok(current_entrypoint)
let new_bin_path = new_path(new_directory.as_ref(), &bin_path)?;
let mut new_entrypoint = new_bin_path.to_string_lossy().to_string();
if args.is_empty().not() {
new_entrypoint.push(' ');
new_entrypoint.push_str(&args);
}

self.set_entrypoint(arch.clone(), new_entrypoint);
Ok(bin_path)
}

/// convert a `PluginManifest` to a `KdlDocument`
Expand Down Expand Up @@ -327,6 +346,11 @@ impl FromStr for PluginManifest {
}
}

pub fn try_get_bin_for_entrypoint(entrypoint: &str) -> (Option<&str>, String) {
let mut split = entrypoint.split_whitespace();
(split.next(), split.collect())
}

#[cfg(test)]
mod test {

Expand Down
33 changes: 21 additions & 12 deletions hipcheck/src/plugin/retrieval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
error::Error,
hc_error,
plugin::{
download_manifest::DownloadManifestEntry, ArchiveFormat, DownloadManifest, HashAlgorithm,
HashWithDigest, PluginId, PluginManifest,
download_manifest::DownloadManifestEntry, try_get_bin_for_entrypoint, ArchiveFormat,
DownloadManifest, HashAlgorithm, HashWithDigest, PluginId, PluginManifest,
},
policy::policy_file::{ManifestLocation, PolicyPlugin},
util::http::agent::agent,
Expand Down Expand Up @@ -121,18 +121,27 @@ fn retrieve_local_plugin(
let mut plugin_manifest = PluginManifest::from_file(plugin_manifest_path)?;
let current_arch = get_current_arch();

let original_entrypoint = plugin_manifest
.update_entrypoint(&current_arch, plugin_cache.plugin_download_dir(&plugin_id))?;
let curr_entrypoint = plugin_manifest.get_entrypoint_for(&current_arch)?;

// @Note - sneaky potential for unexpected behavior if we write local plugin manifest
// to a cache dir that already included a remote download
std::fs::copy(
&original_entrypoint,
plugin_cache
.plugin_download_dir(&plugin_id)
if let Some(curr_bin) = try_get_bin_for_entrypoint(&curr_entrypoint).0 {
// Only do copy if using a path to a binary instead of a PATH-based resolution (i.e.
// `docker _`. We wouldn't want to copy the `docker` binary in this case
if std::fs::exists(curr_bin)? {
let original_entrypoint = plugin_manifest
.update_entrypoint(&current_arch, plugin_cache.plugin_download_dir(&plugin_id))?;

let new_entrypoint = plugin_manifest.get_entrypoint_for(&current_arch)?;
// unwrap is safe here, we just updated the entrypoint for current arch
.join(plugin_manifest.get_entrypoint(&current_arch).unwrap()),
)?;
let new_bin = try_get_bin_for_entrypoint(&new_entrypoint).0.unwrap();

// @Note - sneaky potential for unexpected behavior if we write local plugin manifest
// to a cache dir that already included a remote download
std::fs::copy(
&original_entrypoint,
plugin_cache.plugin_download_dir(&plugin_id).join(new_bin),
)?;
}
}

let plugin_kdl_path = plugin_cache.plugin_kdl(&plugin_id);
write_all(&plugin_kdl_path, &plugin_manifest.to_kdl_formatted_string()).map_err(|e| {
Expand Down