Skip to content

Commit

Permalink
feat: Update and add functions + move to async
Browse files Browse the repository at this point in the history
  • Loading branch information
GeekMasher committed Jun 20, 2024
1 parent 61ffd23 commit c413b7e
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 27 deletions.
10 changes: 7 additions & 3 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async fn main() -> Result<()> {
info!("CodeQL :: {}", codeql);
info!(
"CodeQL Languages Loaded :: {}",
codeql.get_languages()?.len()
codeql.get_languages().await?.len()
);

if list {
Expand Down Expand Up @@ -141,15 +141,19 @@ async fn main() -> Result<()> {
info!("Database :: {}", database);
info!("Creating database :: {}", database.path().display());

codeql.database(&database).overwrite().create()?;
codeql.database(&database).overwrite().create().await?;

// Reload the database after creation
database.reload()?;

let queries = CodeQLQueries::language_default(language.language());

info!("Analyzing database :: {}", database);
let results = codeql.database(&database).queries(queries).analyze()?;
let results = codeql
.database(&database)
.queries(queries)
.analyze()
.await?;

info!("Results :: {:?}", results.get_results().len());
for result in results.get_results() {
Expand Down
12 changes: 10 additions & 2 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ rust-version = "1.70"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["async"]

async = ["dep:async-trait", "dep:tokio"]

[dependencies]
anyhow = "1.0.80"
async-trait = "0.1.77"
chrono = { version = "0.4.34", features = ["serde"] }
git2 = "0.19.0"
glob = "0.3.1"
Expand All @@ -35,8 +39,12 @@ url = { version = "2.5.0", features = ["serde"] }
walkdir = "2.5.0"
time = "0.3.36"

# Async
async-trait = { version = "0.1.77", optional = true }
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "time"], optional = true}

[dev-dependencies]
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread", "time"] }
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "time"] }

[[example]]
name = "codeql-packs"
Expand Down
46 changes: 31 additions & 15 deletions core/src/codeql/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ pub struct CodeQL {
threads: usize,
/// Amount of RAM to use
ram: Option<usize>,

/// The search path for the CodeQL CLI
search_path: Vec<String>,
/// Additional packs to use
Expand All @@ -36,6 +35,11 @@ impl CodeQL {
Self::default()
}

/// Get the CodeQL CLI path
pub fn path(&self) -> &PathBuf {
&self.path
}

/// Initialize a new CodeQL Builder instance
pub fn init() -> CodeQLBuilder {
CodeQLBuilder::default()
Expand Down Expand Up @@ -65,13 +69,13 @@ impl CodeQL {
None
}

/// Run a CodeQL command
pub fn run(&self, args: Vec<&str>) -> Result<String, GHASError> {
/// Run a CodeQL command asynchronously
pub async fn run(&self, args: Vec<&str>) -> Result<String, GHASError> {
debug!("{:?}", args);
let mut cmd = std::process::Command::new(&self.path);
let mut cmd = tokio::process::Command::new(&self.path);
cmd.args(args);

let output = cmd.output()?;
let output = cmd.output().await?;

if output.status.success() {
debug!("CodeQL Command Success: {:?}", output.status.to_string());
Expand All @@ -91,10 +95,11 @@ impl CodeQL {

/// Get the version of the CodeQL CLI
pub fn version(&self) -> Result<String, GHASError> {
match self.run(vec!["version", "--format", "terse"]) {
Ok(v) => Ok(v.trim().to_string()),
Err(e) => Err(e),
}
std::process::Command::new(&self.path)
.args(&["version", "--format", "terse"])
.output()
.map(|v| String::from_utf8_lossy(&v.stdout).to_string())
.map_err(|e| GHASError::CodeQLError(e.to_string()))
}

/// Get the programming languages supported by the CodeQL CLI.
Expand All @@ -106,36 +111,46 @@ impl CodeQL {
/// ```no_run
/// use ghastoolkit::CodeQL;
///
/// # #[tokio::main]
/// # async fn main() {
/// let codeql = CodeQL::default();
///
/// let languages = codeql.get_languages()
/// .await
/// .expect("Failed to get languages");
///
/// for language in languages {
/// println!("Language: {}", language.pretty());
/// // Do something with the language
/// }
///
pub fn get_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
/// # }
/// ```
pub async fn get_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
Ok(self
.get_all_languages()?
.get_all_languages()
.await?
.into_iter()
.filter(|l| !l.is_secondary() || !l.is_none())
.collect())
}

/// Get the secondary languages supported by the CodeQL CLI
pub fn get_secondary_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
pub async fn get_secondary_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
Ok(self
.get_all_languages()?
.get_all_languages()
.await?
.into_iter()
.filter(|l| l.is_secondary())
.collect())
}

/// Get all languages supported by the CodeQL CLI
pub fn get_all_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
match self.run(vec!["resolve", "languages", "--format", "json"]) {
pub async fn get_all_languages(&self) -> Result<Vec<CodeQLLanguage>, GHASError> {
match self
.run(vec!["resolve", "languages", "--format", "json"])
.await
{
Ok(v) => {
let languages: ResolvedLanguages = serde_json::from_str(&v)?;
let mut result = Vec::new();
Expand All @@ -153,6 +168,7 @@ impl CodeQL {

impl Display for CodeQL {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Run the version command synchronously
match self.version() {
Ok(v) => write!(f, "CodeQL('{}', '{}')", self.path.display(), v),
Err(_) => write!(f, "CodeQL('{}')", self.path.display()),
Expand Down
8 changes: 4 additions & 4 deletions core/src/codeql/database/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ impl<'db, 'ql> CodeQLDatabaseHandler<'db, 'ql> {
}

/// Create a new CodeQL Database using the provided database
pub fn create(&mut self) -> Result<(), GHASError> {
pub async fn create(&mut self) -> Result<(), GHASError> {
let args = self.create_cmd()?;

// Create path
if !self.database.path().exists() {
std::fs::create_dir_all(self.database.path())?;
}

self.codeql.run(args)?;
self.codeql.run(args).await?;

Ok(())
}
Expand Down Expand Up @@ -133,10 +133,10 @@ impl<'db, 'ql> CodeQLDatabaseHandler<'db, 'ql> {
path
}
/// Analyze the database
pub fn analyze(&self) -> Result<Sarif, GHASError> {
pub async fn analyze(&self) -> Result<Sarif, GHASError> {
let args = self.analyze_cmd()?;

self.codeql.run(args)?;
self.codeql.run(args).await?;
Sarif::try_from(self.output.clone())
}

Expand Down
5 changes: 5 additions & 0 deletions core/src/codeql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
//! ```no_run
//! use ghastoolkit::codeql::{CodeQL, CodeQLDatabase};
//!
//! # #[tokio::main]
//! # async fn main() {
//! let codeql = CodeQL::default();
//!
//! // Create a new CodeQL database
Expand All @@ -55,11 +57,14 @@
//! codeql.database(&database)
//! .overwrite()
//! .create()
//! .await
//! .expect("Failed to create CodeQL database");
//!
//! let results = codeql.database(&database)
//! .analyze()
//! .await
//! .expect("Failed to analyze CodeQL database");
//! # }
//!```

/// This module contains the codeql struct and its methods
Expand Down
92 changes: 89 additions & 3 deletions core/src/codeql/packs/pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,82 @@ impl CodeQLPack {
self.pack_type.clone()
}

/// Download a CodeQL Pack using its name (namespace/name[@version])
///
/// ```bash
/// codeql pack download <name>
/// ```
#[cfg(feature = "async")]
pub async fn download(
codeql: &crate::CodeQL,
name: impl Into<String>,
) -> Result<Self, GHASError> {
let name = name.into();
if let Some((namespace, mut packname)) = &name.split_once('/') {
let version: Option<String> = if let Some((pname, version)) = packname.split_once('@') {
packname = pname;
Some(version.to_string())
} else {
None
};

codeql.run(vec!["pack", "download", name.as_str()]).await?;

// CodeQL installs the pack in `~/.codeql/packages/{namespace}/{name}/{version}`
let path = PathBuf::from("~/.codeql/packages")
.join(namespace)
.join(packname)
.join(version.unwrap_or_default());
Ok(Self::new(path))
} else {
Err(GHASError::CodeQLPackError(format!(
"Invalid Pack Name: {}",
name
)))
}
}

/// Install the CodeQL Pack Dependencies
///
/// ```bash
/// codeql pack install <path>
/// ```
#[cfg(feature = "async")]
pub async fn install(&self, codeql: &crate::CodeQL) -> Result<(), GHASError> {
codeql
.run(vec!["pack", "install", self.path().to_str().unwrap()])
.await
.map(|_| ())
}

/// Upgrade CodeQL Pack Dependencies
#[cfg(feature = "async")]
pub async fn upgrade(&self, codeql: &crate::CodeQL) -> Result<(), GHASError> {
codeql
.run(vec!["pack", "upgrade", self.path().to_str().unwrap()])
.await
.map(|_| ())
}

/// Publish the CodeQL Pack
///
/// ```bash
/// codeql pack publish <path>
/// ```
#[cfg(feature = "async")]
pub async fn publish(
&self,
codeql: &crate::CodeQL,
token: impl Into<String>,
) -> Result<(), GHASError> {
Ok(tokio::process::Command::new(codeql.path())
.env("CODEQL_REGISTRIES_AUTH", token.into())
.args(vec!["pack", "publish", self.path().to_str().unwrap()])
.output()
.await
.map(|_| ())?)
}

/// Load a QLPack from a path (root directory or qlpack.yml file)
pub fn load(path: impl Into<PathBuf>) -> Result<Self, GHASError> {
// Path is the directory
Expand Down Expand Up @@ -105,10 +181,10 @@ impl CodeQLPack {
/// Based on the loaded YAML, determine the pack type
fn get_pack_type(pack_yaml: &PackYaml) -> CodeQLPackType {
if let Some(library) = pack_yaml.library {
if library {
return CodeQLPackType::Library;
} else if pack_yaml.data_extensions.is_some() {
if library && pack_yaml.data_extensions.is_some() {
return CodeQLPackType::Models;
} else if library {
return CodeQLPackType::Library;
}
} else if pack_yaml.tests.is_some() {
return CodeQLPackType::Testing;
Expand All @@ -120,6 +196,16 @@ impl CodeQLPack {
}
}

impl Display for CodeQLPack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(version) = self.version() {
write!(f, "{} ({}) - v{}", self.name(), self.pack_type(), version)
} else {
write!(f, "{} ({})", self.name(), self.pack_type(),)
}
}
}

/// CodeQL Pack Type
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum CodeQLPackType {
Expand Down
14 changes: 14 additions & 0 deletions core/src/codeql/packs/packs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ impl CodeQLPacks {
pub fn sort(&mut self) {
self.packs.sort_by(|a, b| a.pack_type().cmp(&b.pack_type()));
}
/// Get the packs
pub fn packs(&self) -> &[CodeQLPack] {
&self.packs
}
/// Merge two CodeQL Packs
pub fn merge(&mut self, other: &mut Self) {
self.packs.append(&mut other.packs);
}

/// Load CodeQL Packs from a directory. It will recursively search for `qlpack.yml` files.
pub fn load(path: impl Into<PathBuf>) -> Result<Self> {
Expand All @@ -46,3 +54,9 @@ impl IntoIterator for CodeQLPacks {
self.packs.into_iter()
}
}

impl Extend<CodeQLPack> for CodeQLPacks {
fn extend<T: IntoIterator<Item = CodeQLPack>>(&mut self, iter: T) {
self.packs.extend(iter);
}
}

0 comments on commit c413b7e

Please sign in to comment.