Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
152 changes: 109 additions & 43 deletions src/list.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,61 @@
use crate::config::Config;
use crate::log::debug;
use crate::utils;
use crate::utils::api::ProjectSelector;
use serde_json::json;
use std::path::Path;

pub fn run(
config: &Config,
issues: &bool,
sca_issues: &bool,
json: &bool,
page: &Option<u16>,
page_size: &Option<u16>,
scan_id: &Option<String>,
) {
let project_name = utils::generic::determine_project_name(None);
println!();
if *sca_issues {
#[derive(Default)]
pub struct ListArgs {
pub issues: bool,
pub sca_issues: bool,
pub json: bool,
pub page: Option<u16>,
pub page_size: Option<u16>,
pub scan_id: Option<String>,
pub selector: ProjectSelector,
}

pub fn run(config: &Config, args: ListArgs) {
let ListArgs {
issues,
sca_issues,
json,
page,
page_size,
scan_id,
selector,
} = args;
if !json {
println!();
}
if sca_issues {
// Only an explicit --project-name/--repo scopes the SCA listing: the
// endpoint takes `project`, but unflagged `--sca-issues` has always
// returned the company-wide latest scan and narrowing that silently is
// not this change's to make. Without a selector the CWD basename stays
// what it was — error copy only.
let resolved = (scan_id.is_none() && selector.is_set())
.then(|| utils::api::resolve_project_or_exit(&config.get_url(), &selector));
let project_name = resolved
.as_ref()
.map(|r| r.query_name.clone())
.unwrap_or_else(|| {
utils::generic::get_current_working_directory().unwrap_or("unknown".to_string())
});
let sca_issues_response = match utils::api::get_sca_issues(
&config.get_url(),
Some((*page).unwrap_or(1)),
*page_size,
Some(page.unwrap_or(1)),
page_size,
scan_id.clone(),
resolved.as_ref().map(|r| r.query_name.as_str()),
) {
Ok(response) => response,
Err(e) => {
debug(&format!("Error Sending Request: {}", e));
if e.to_string().contains("404") {
if scan_id.is_some() {
log::error!("Scan with ID '{}' doesn't exist or has no SCA issues. Please run 'corgea scan' to create a new scan for this project.", scan_id.as_ref().unwrap());
if let Some(id) = &scan_id {
log::error!("Scan with ID '{}' doesn't exist or has no SCA issues. Please run 'corgea scan' to create a new scan for this project.", id);
} else {
log::error!("No SCA issues found for project '{}'. Please run 'corgea scan' to create a new scan for this project.", project_name);
}
Expand All @@ -44,7 +72,7 @@ pub fn run(
}
};

if *json {
if json {
let output = serde_json::json!({
"page": sca_issues_response.page,
"total_pages": sca_issues_response.total_pages,
Expand Down Expand Up @@ -107,22 +135,38 @@ pub fn run(
Some(sca_issues_response.page),
Some(sca_issues_response.total_pages),
);
} else if *issues {
return;
}

if issues {
// The --scan-id issue route hits /scan/{id}/issues and ignores the
// project, so it needs no resolution.
let resolved = scan_id

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selector flags are silently ignored when we pass --scan-id, should we add a warning log? or handle it during args parsing?

.is_none()
.then(|| utils::api::resolve_project_or_exit(&config.get_url(), &selector));
let project_name = resolved
.as_ref()
.map(|r| r.query_name.clone())
.unwrap_or_default();
let issues_response = match utils::api::get_scan_issues(
&config.get_url(),
&project_name,
Some((*page).unwrap_or(1)),
*page_size,
Some(page.unwrap_or(1)),
page_size,
scan_id.clone(),
) {
Ok(response) => response,
Err(e) => {
debug(&format!("Error Sending Request: {}", e));
if e.to_string().contains("404") {
if scan_id.is_some() {
log::error!("Scan with ID '{}' doesn't exist. Please run 'corgea scan' to create a new scan for this project.", scan_id.as_ref().unwrap());
} else {
log::error!("Project with name '{}' doesn't exist. Please run 'corgea scan' to create a new scan for this project.", project_name);
// `resolved` is None exactly on the --scan-id route.
match &resolved {
None => log::error!("Scan with ID '{}' doesn't exist. Please run 'corgea scan' to create a new scan for this project.", scan_id.as_ref().unwrap()),
Some(r) if r.confirmed => log::error!("Project '{}' has no issues yet. Run 'corgea scan' to create a scan for this project.", project_name),
Some(r) => log::error!(
"No Corgea project found for {}. Run 'corgea scan' to create one, or pass --project-name <NAME>.",
r.tried_label
),
}
} else {
log::error!(
Expand All @@ -140,14 +184,10 @@ pub fn run(
let mut blocking_rules: std::collections::HashMap<String, String> =
std::collections::HashMap::new();

if scan_id.is_some() {
if let Some(id) = &scan_id {
let mut page: u32 = 1;
loop {
match utils::api::check_blocking_rules(
&config.get_url(),
scan_id.as_ref().unwrap(),
Some(page),
) {
match utils::api::check_blocking_rules(&config.get_url(), id, Some(page)) {
Ok(rules) => {
if rules.block {
render_blocking_rules = true;
Expand All @@ -170,7 +210,7 @@ pub fn run(
}
}

if *json {
if json {
let mut json = serde_json::json!({
"page": issues_response.page,
"total_pages": issues_response.total_pages,
Expand Down Expand Up @@ -259,26 +299,28 @@ pub fn run(

utils::terminal::print_table(table, issues_response.page, issues_response.total_pages);
} else {
let resolved = utils::api::resolve_project_or_exit(&config.get_url(), &selector);
let project_name = &resolved.query_name;
let (scans, page, total_pages) = match utils::api::query_scan_list(
&config.get_url(),
Some(&project_name),
*page,
*page_size,
Some(project_name),
page,
page_size,
) {
Ok(scans) => {
let page = scans.page;
let total_pages = scans.total_pages;
let filtered_scans: Vec<utils::api::ScanResponse> = scans
.scans
.unwrap_or_default()
.into_iter()
.filter(|scan| scan.project == project_name)
.collect();
(filtered_scans, page, total_pages)
// The server already filtered by the resolved project; the old
// client-side `scan.project == cwd_basename` pass would discard
// every repo-resolved scan. (COR-1577)
(scans.scans.unwrap_or_default(), page, total_pages)
}
Err(e) => {
if e.to_string().contains("404") {
log::error!("Project with name '{}' doesn't exist. Please run 'corgea scan' to create a new scan for this project.", project_name);
log::error!(
"No Corgea project found for {}. Run 'corgea scan' to create one, or pass --project-name <NAME>.",
resolved.tried_label
);
} else {
log::error!(
"Unable to fetch scans. Please check your connection and ensure that:\n\
Expand All @@ -290,13 +332,36 @@ pub fn run(
std::process::exit(1);
}
};
if *json {
if json {
let output = json!({
"page": page,
"total_pages": total_pages,
"results": scans
});
// The envelope prints first so JSON consumers get valid stdout
// even when the miss below exits 1.
println!("{}", serde_json::to_string_pretty(&output).unwrap());
}
// An unresolved project is a miss (exit 1, as --issues and `wait`); a
// confirmed project with no scans is a valid empty result. So is an
// explicit --project-name: /scans answers 200-empty either way, so the
// caller's own exact name is the better authority than a guess that it
// does not exist.
if scans.is_empty() && !resolved.confirmed && selector.name.is_none() {
log::error!(
"No Corgea project found for {}. Run 'corgea scan' to create one, or pass --project-name <NAME>.",
resolved.tried_label
);
std::process::exit(1);
}
if json {
return;
}
if scans.is_empty() {
println!(
"Project '{}' has no scans yet. Run 'corgea scan' to create one.",
project_name
);
return;
}
let mut table = vec![vec![
Expand All @@ -321,6 +386,7 @@ pub fn run(
} else {
formatted_repo
};

table.push(vec![
scan.id.clone(),
scan.project.clone(),
Expand Down
74 changes: 65 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,27 @@ enum Commands {
project_name: Option<String>,
},
/// Wait for the latest in progress scan
Wait { scan_id: Option<String> },
Wait {
scan_id: Option<String>,
#[arg(
long,
conflicts_with = "repo",
Comment thread
juangaitanv marked this conversation as resolved.
help = "Query this exact Corgea project name directly (skips repo auto-resolution)."
)]
project_name: Option<String>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use NonEmptyStringValueParser for project_name and repo?

#[arg(
long,
help = "Resolve the project from this repo (org/repo slug or remote URL) instead of the git remote."
)]
repo: Option<String>,
#[arg(
long,
requires = "scan_id",
value_parser = clap::builder::NonEmptyStringValueParser::new(),
help = "Use this known Corgea project id for the result link, skipping project resolution. Requires a scan id, which is what the id then belongs to."
)]
Comment thread
cursor[bot] marked this conversation as resolved.
project_id: Option<String>,
},
/// List something, by default it lists the scans
#[command(alias = "ls")]
List {
Expand All @@ -166,6 +186,19 @@ enum Commands {

#[arg(long, value_parser = clap::value_parser!(u16), help = "Number of items per page")]
page_size: Option<u16>,

#[arg(
long,
conflicts_with = "repo",
help = "Query this exact Corgea project name directly (skips repo auto-resolution)."
)]
project_name: Option<String>,
Comment thread
cursor[bot] marked this conversation as resolved.

#[arg(
long,
help = "Resolve the project from this repo (org/repo slug or remote URL) instead of the git remote."
)]
repo: Option<String>,
Comment thread
cursor[bot] marked this conversation as resolved.
},
/// Inspect something, by default it will inspect a scan
Inspect {
Expand Down Expand Up @@ -663,9 +696,24 @@ fn main() {
),
}
}
Some(Commands::Wait { scan_id }) => {
Some(Commands::Wait {
scan_id,
project_name,
repo,
project_id,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
wait::run(&corgea_config, scan_id.clone(), None);
wait::run(
&corgea_config,
wait::WaitArgs {
scan_id: scan_id.clone(),
selector: utils::api::ProjectSelector {
name: project_name.clone(),
repo: repo.clone(),
},
project_id: project_id.clone(),
},
);
}
Some(Commands::List {
issues,
Expand All @@ -674,6 +722,8 @@ fn main() {
page_size,
scan_id,
sca_issues,
project_name,
repo,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
if *issues && *sca_issues {
Expand All @@ -686,12 +736,18 @@ fn main() {
}
list::run(
&corgea_config,
issues,
sca_issues,
json,
page,
page_size,
scan_id,
list::ListArgs {
issues: *issues,
sca_issues: *sca_issues,
json: *json,
page: *page,
page_size: *page_size,
scan_id: scan_id.clone(),
selector: utils::api::ProjectSelector {
name: project_name.clone(),
repo: repo.clone(),
},
},
);
}
Some(Commands::Inspect {
Expand Down
28 changes: 24 additions & 4 deletions src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,18 @@ pub fn run_semgrep(config: &Config, project_name: Option<String>) {

let output = run_command(&base_command.to_string(), command);

if let Some(result) = parse_scan(config, output, true, project_name) {
crate::wait::run(config, Some(result.scan_id), result.project_id);
if let Some(result) = parse_scan(config, output, true, project_name.clone()) {
crate::wait::run(
config,
crate::wait::WaitArgs {
scan_id: Some(result.scan_id),
selector: crate::utils::api::ProjectSelector {
name: project_name,
..Default::default()
},
project_id: result.project_id,
},
);
}
}

Expand All @@ -80,8 +90,18 @@ pub fn run_snyk(config: &Config, project_name: Option<String>) {

let output = run_command(&base_command.to_string(), command);

if let Some(result) = parse_scan(config, output, true, project_name) {
crate::wait::run(config, Some(result.scan_id), result.project_id);
if let Some(result) = parse_scan(config, output, true, project_name.clone()) {
crate::wait::run(
config,
crate::wait::WaitArgs {
scan_id: Some(result.scan_id),
selector: crate::utils::api::ProjectSelector {
name: project_name,
..Default::default()
},
project_id: result.project_id,
},
);
}
}

Expand Down
Loading
Loading