Skip to content

Commit

Permalink
Remove phylum batch subcommand (#1489)
Browse files Browse the repository at this point in the history
  • Loading branch information
cd-work authored Sep 6, 2024
1 parent 5aec014 commit 907fa31
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 189 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `phylum project list --no-group` flag to only show personal projects
- Organization support for `phylum group` subcommands

### Removed

- `phylum batch` subcommand

## 6.6.6 - 2024-07-12

### Added
Expand Down
37 changes: 0 additions & 37 deletions cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,43 +427,6 @@ pub fn add_subcommands(command: Command) -> Command {
.help("Disable generation of lockfiles from manifests"),
]),
)
.subcommand(
Command::new("batch")
.hide(true)
.about("Submits a batch of requests to the processing system")
.args(&[
Arg::new("file")
.short('f')
.long("file")
.value_name("FILE")
.help(
"File (or piped stdin) containing the list of packages (format \
`<name>:<version>`)",
)
.value_hint(ValueHint::FilePath),
Arg::new("type")
.short('t')
.long("type")
.value_name("TYPE")
.help("Package ecosystem type")
.value_parser([
"npm", "rubygems", "pypi", "maven", "nuget", "golang", "cargo",
])
.required(true),
Arg::new("label").short('l').long("label").help("Label to use for analysis"),
Arg::new("project")
.short('p')
.long("project")
.value_name("PROJECT_NAME")
.help("Project to use for analysis"),
Arg::new("group")
.short('g')
.long("group")
.value_name("GROUP_NAME")
.help("Group to use for analysis")
.requires("project"),
]),
)
.subcommand(Command::new("version").about("Display application version"))
.subcommand(
Command::new("group")
Expand Down
2 changes: 1 addition & 1 deletion cli/src/bin/phylum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ async fn handle_commands() -> CommandResult {
"package" => packages::handle_get_package(&Spinner::wrap(api).await?, sub_matches).await,
"history" => jobs::handle_history(&Spinner::wrap(api).await?, sub_matches).await,
"group" => group::handle_group(&Spinner::wrap(api).await?, sub_matches, config).await,
"analyze" | "batch" => jobs::handle_submission(&Spinner::wrap(api).await?, &matches).await,
"analyze" => jobs::handle_analyze(&Spinner::wrap(api).await?, sub_matches).await,
"init" => init::handle_init(&Spinner::wrap(api).await?, sub_matches).await,
"status" => status::handle_status(sub_matches).await,
"org" => org::handle_org(&Spinner::wrap(api).await?, sub_matches, config).await,
Expand Down
228 changes: 82 additions & 146 deletions cli/src/commands/jobs.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::fs;
#[cfg(feature = "vulnreach")]
use std::io;
use std::str::FromStr;
use std::{fs, io};

use anyhow::{anyhow, Context, Result};
use console::style;
use log::debug;
use phylum_lockfile::ParseError;
use phylum_project::DepfileConfig;
use phylum_types::types::common::{JobId, ProjectId};
use phylum_types::types::package::{PackageDescriptor, PackageType};
use phylum_types::types::package::PackageDescriptor;
use reqwest::StatusCode;
#[cfg(feature = "vulnreach")]
use vulnreach_types::{Job, JobPackage};
Expand Down Expand Up @@ -85,133 +87,73 @@ pub async fn handle_history(api: &PhylumApi, matches: &clap::ArgMatches) -> Comm
Ok(ExitCode::Ok)
}

/// Handles submission of packages to the system for analysis and
/// displays summary information about the submitted package(s)
pub async fn handle_submission(api: &PhylumApi, matches: &clap::ArgMatches) -> CommandResult {
let mut ignored_packages: Vec<PackageDescriptor> = vec![];
let mut packages = vec![];
let mut synch = false; // get status after submission
let mut pretty_print = false;
let jobs_project;
let label;

if let Some(matches) = matches.subcommand_matches("analyze") {
let sandbox_generation = !matches.get_flag("skip-sandbox");
let generate_lockfiles = !matches.get_flag("no-generation");
label = matches.get_one::<String>("label");
pretty_print = !matches.get_flag("json");
synch = true;

jobs_project = JobsProject::new(api, matches).await?;

// Get .phylum_project path
let current_project = phylum_project::get_current_project();
let project_root = current_project.as_ref().map(|p| p.root());

for depfile in jobs_project.depfiles {
let parse_result = parse::parse_depfile(
&depfile.path,
project_root,
Some(&depfile.depfile_type),
sandbox_generation,
generate_lockfiles,
);

// Map dedicated exit codes for failures due to disabled generation or
// unknown dependency file format.
let parsed_depfile = match parse_result {
Ok(parsed_depfile) => parsed_depfile,
Err(err @ ParseError::ManifestWithoutGeneration(_)) => {
print_user_failure!("Could not parse manifest: {}", err);
return Ok(ExitCode::ManifestWithoutGeneration);
},
Err(err @ ParseError::UnknownManifestFormat(_)) => {
print_user_failure!("Could not parse manifest: {}", err);
return Ok(ExitCode::UnknownManifestFormat);
},
Err(ParseError::Other(err)) => {
return Err(err).with_context(|| {
format!(
"Could not parse dependency file {:?} as {:?} type",
depfile.path.display(),
depfile.depfile_type
)
});
},
};

if pretty_print {
print_user_success!(
"Successfully parsed dependency file {:?} as type {:?}",
parsed_depfile.path,
parsed_depfile.format.name()
);
}

let mut analysis_packages =
AnalysisPackageDescriptor::descriptors_from_lockfile(parsed_depfile);
packages.append(&mut analysis_packages);
}

if let Some(base) = matches.get_one::<String>("base") {
let base_text = fs::read_to_string(base)?;
ignored_packages = serde_json::from_str(&base_text)?;
}
} else if let Some(matches) = matches.subcommand_matches("batch") {
jobs_project = JobsProject::new(api, matches).await?;

let mut eof = false;
let mut line = String::new();
let mut reader: Box<dyn io::BufRead> = if let Some(file) = matches.get_one::<String>("file")
{
// read entries from the file
Box::new(io::BufReader::new(std::fs::File::open(file).unwrap()))
} else {
// read from stdin
log::info!("Waiting on stdin...");
Box::new(io::BufReader::new(io::stdin()))
};
/// Handle `phylum analyze` subcommand.
pub async fn handle_analyze(api: &PhylumApi, matches: &clap::ArgMatches) -> CommandResult {
let sandbox_generation = !matches.get_flag("skip-sandbox");
let generate_lockfiles = !matches.get_flag("no-generation");
let label = matches.get_one::<String>("label");
let pretty_print = !matches.get_flag("json");

let request_type = {
let package_type = matches.get_one::<String>("type").unwrap();
PackageType::from_str(package_type)
.map_err(|_| anyhow!("invalid package type: {}", package_type))?
let jobs_project = JobsProject::new(api, matches).await?;

// Get .phylum_project path.
let current_project = phylum_project::get_current_project();
let project_root = current_project.as_ref().map(|p| p.root());

let mut packages = Vec::new();
for depfile in jobs_project.depfiles {
let parse_result = parse::parse_depfile(
&depfile.path,
project_root,
Some(&depfile.depfile_type),
sandbox_generation,
generate_lockfiles,
);

// Map dedicated exit codes for failures due to disabled generation or
// unknown dependency file format.
let parsed_depfile = match parse_result {
Ok(parsed_depfile) => parsed_depfile,
Err(err @ ParseError::ManifestWithoutGeneration(_)) => {
print_user_failure!("Could not parse manifest: {}", err);
return Ok(ExitCode::ManifestWithoutGeneration);
},
Err(err @ ParseError::UnknownManifestFormat(_)) => {
print_user_failure!("Could not parse manifest: {}", err);
return Ok(ExitCode::UnknownManifestFormat);
},
Err(ParseError::Other(err)) => {
return Err(err).with_context(|| {
format!(
"Could not parse dependency file {:?} as {:?} type",
depfile.path.display(),
depfile.depfile_type
)
});
},
};

label = matches.get_one::<String>("label");

while !eof {
match reader.read_line(&mut line) {
Ok(0) => eof = true,
Ok(_) => {
line.pop();
let mut pkg_info = line.split(':').collect::<Vec<&str>>();
if pkg_info.len() < 2 {
debug!("Invalid package input: `{}`", line);
continue;
}
let pkg_version = pkg_info.pop().unwrap();
let pkg_name = pkg_info.join(":");

packages.push(AnalysisPackageDescriptor::PackageDescriptor(
PackageDescriptor {
name: pkg_name.to_owned(),
version: pkg_version.to_owned(),
package_type: request_type.to_owned(),
}
.into(),
));
line.clear();
},
Err(err) => {
return Err(anyhow!(err));
},
}
if pretty_print {
print_user_success!(
"Successfully parsed dependency file {:?} as type {:?}",
parsed_depfile.path,
parsed_depfile.format.name()
);
}
} else {
unreachable!();

let mut analysis_packages =
AnalysisPackageDescriptor::descriptors_from_lockfile(parsed_depfile);
packages.append(&mut analysis_packages);
}

let ignored_packages: Vec<PackageDescriptor> = match matches.get_one::<String>("base") {
Some(base) => {
let base_text = fs::read_to_string(base)?;
serde_json::from_str(&base_text)?
},
None => Vec::new(),
};

// Avoid request error without dependencies.
if packages.is_empty() {
print_user_warning!("No packages found in dependency file");
Expand All @@ -231,31 +173,25 @@ pub async fn handle_submission(api: &PhylumApi, matches: &clap::ArgMatches) -> C

if pretty_print {
print_user_success!("Job ID: {}", job_id);
}

if synch {
if pretty_print {
#[cfg(feature = "vulnreach")]
let packages: Vec<_> = packages
.into_iter()
.filter_map(|pkg| match pkg {
AnalysisPackageDescriptor::PackageDescriptor(package) => {
Some(package.package_descriptor)
},
AnalysisPackageDescriptor::Purl(_) => None,
})
.collect();
#[cfg(feature = "vulnreach")]
if let Err(err) = vulnreach(api, matches, packages, job_id.to_string()).await {
print_user_failure!("Reachability analysis failed: {err:?}");
}
#[cfg(feature = "vulnreach")]
let packages: Vec<_> = packages
.into_iter()
.filter_map(|pkg| match pkg {
AnalysisPackageDescriptor::PackageDescriptor(package) => {
Some(package.package_descriptor)
},
AnalysisPackageDescriptor::Purl(_) => None,
})
.collect();
#[cfg(feature = "vulnreach")]
if let Err(err) = vulnreach(api, matches, packages, job_id.to_string()).await {
print_user_failure!("Reachability analysis failed: {err:?}");
}

debug!("Requesting status...");
print_job_status(api, &job_id, ignored_packages, pretty_print).await
} else {
Ok(ExitCode::Ok)
}

debug!("Requesting status...");
print_job_status(api, &job_id, ignored_packages, pretty_print).await
}

/// Perform vulnerability reachability analysis.
Expand Down Expand Up @@ -303,7 +239,7 @@ async fn vulnreach(
Ok(())
}

/// Project information for analyze/batch.
/// Project information for analyze.
struct JobsProject {
project_id: ProjectId,
group: Option<String>,
Expand Down
2 changes: 1 addition & 1 deletion lockfile/src/parsers/gem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct Section<'a> {

impl<'a> Section<'a> {
/// Parse lockfile into dependency sections.
fn from_lockfile(mut input: &'a str) -> IResult<&str, Vec<Self>> {
fn from_lockfile(mut input: &'a str) -> IResult<&'a str, Vec<Self>> {
let mut sections = Vec::new();

while !input.is_empty() {
Expand Down
4 changes: 2 additions & 2 deletions lockfile/src/parsers/pypi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ fn package_hash(input: &str) -> IResult<&str, &str> {
/// A combinator that takes a parser `inner` and produces a parser that also
/// consumes both leading and trailing whitespace, returning the output of
/// `inner`.
fn ws<'a, F>(inner: F) -> impl FnMut(&'a str) -> IResult<&str, &str>
fn ws<'a, F>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str>
where
F: Fn(&'a str) -> IResult<&str, &str>,
F: Fn(&'a str) -> IResult<&'a str, &'a str>,
{
delimited(nl_space0, inner, nl_space0)
}
Expand Down
4 changes: 2 additions & 2 deletions lockfile/src/parsers/spdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ fn parse_external_refs(input: &str) -> IResult<&str, ExternalRefs> {
/// A combinator that takes a parser `inner` and produces a parser that also
/// consumes both leading and trailing whitespace, returning the output of
/// `inner`.
fn ws<'a, F>(inner: F) -> impl FnMut(&'a str) -> IResult<&str, &str>
fn ws<'a, F>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str>
where
F: Fn(&'a str) -> IResult<&str, &str>,
F: Fn(&'a str) -> IResult<&'a str, &'a str>,
{
delimited(multispace0, inner, multispace0)
}

0 comments on commit 907fa31

Please sign in to comment.