Skip to content

Commit 06e09d3

Browse files
authored
Merge pull request #23 from advanced-security/docs-and-logging
Docs and logging
2 parents bade8f2 + 28cc532 commit 06e09d3

File tree

4 files changed

+575
-92
lines changed

4 files changed

+575
-92
lines changed

src/action.rs

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
#![allow(dead_code)]
2+
//! Action module for defining and handling the GitHub Action's inputs, outputs, and core functionality
3+
//!
4+
//! This module contains the Action struct which represents the GitHub Action and implements
5+
//! the necessary functionality to process inputs, validate configurations, and manage outputs.
26
use std::path::PathBuf;
37

48
use anyhow::{Context, Result};
59
use ghactions::prelude::*;
610
use ghactions_core::repository::reference::RepositoryReference as Repository;
711
use ghastoolkit::{CodeQL, CodeQLPack, codeql::CodeQLLanguage};
812

13+
/// ASCII art banner for the CodeQL Extractor Action
914
pub const BANNER: &str = r#" ___ _ ____ __ __ _ _ _
1015
/ __\___ __| | ___ /___ \/ / /__\_ _| |_ /_\ ___| |_
1116
/ / / _ \ / _` |/ _ \// / / / /_\ \ \/ / __|//_\\ / __| __|
1217
/ /__| (_) | (_| | __/ \_/ / /___//__ > <| |_/ _ \ (__| |_
1318
\____/\___/ \__,_|\___\___,_\____/\__/ /_/\_\\__\_/ \_/\___|\__|"#;
19+
20+
/// Version of the CodeQL Extractor Action, pulled from Cargo.toml
1421
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
22+
23+
/// Authors of the CodeQL Extractor Action, pulled from Cargo.toml
1524
pub const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
1625

1726
/// This action is for 3rd party CodeQL extractors to be used in GitHub Actions
@@ -94,6 +103,22 @@ pub struct Action {
94103
}
95104

96105
impl Action {
106+
/// Returns the GitHub Token for the action
107+
pub fn get_token(&self) -> String {
108+
if self.token.is_empty() {
109+
std::env::var("GITHUB_TOKEN").unwrap_or_default()
110+
} else {
111+
self.token.clone()
112+
}
113+
}
114+
115+
/// Returns the working directory for the action
116+
///
117+
/// If no working directory is provided, the current directory is used.
118+
/// Otherwise, the provided directory is resolved to an absolute path.
119+
///
120+
/// # Returns
121+
/// - `Result<PathBuf>`: The resolved working directory path
97122
pub fn working_directory(&self) -> Result<PathBuf> {
98123
if self.working_directory.is_empty() {
99124
log::debug!("No working directory provided, using the current directory");
@@ -108,34 +133,102 @@ impl Action {
108133
))
109134
}
110135

111-
/// Gets the repository to use for the extractor. If the repository is not provided,
112-
/// it will use the repository that the action is running in.
136+
/// Gets the repository references for the extractors
137+
///
138+
/// If no extractor repositories are provided, the current repository is used.
139+
/// Otherwise, the provided repositories are parsed into Repository objects.
140+
///
141+
/// # Returns
142+
/// - `Result<Vec<Repository>>`: A list of parsed repository references
113143
pub fn extractor_repository(&self) -> Result<Vec<Repository>> {
114144
if self.extractors.is_empty() {
115145
log::debug!("No extractor repository provided, using the current repository");
116146
return Ok(vec![Repository::parse(&self.get_repository()?)?]);
117147
}
118148

119-
log::debug!("Using the provided extractor repository");
149+
log::debug!(
150+
"Using the provided extractor repositories: {:?}",
151+
self.extractors
152+
);
120153

121-
Ok(self
154+
let repos: Vec<Repository> = self
122155
.extractors
123156
.iter()
124-
.filter_map(|ext| {
125-
Repository::parse(ext)
126-
.context(format!("Failed to parse extractor repository `{ext}`"))
127-
.ok()
157+
.filter_map(|ext| match Repository::parse(ext) {
158+
Ok(repo) => {
159+
log::debug!(
160+
"Successfully parsed repository: {} / {}",
161+
repo.owner,
162+
repo.name
163+
);
164+
Some(repo)
165+
}
166+
Err(e) => {
167+
log::warn!("Failed to parse extractor repository `{}`: {}", ext, e);
168+
None
169+
}
128170
})
129-
.collect::<Vec<Repository>>())
171+
.collect();
172+
173+
log::debug!("Parsed {} repositories", repos.len());
174+
Ok(repos)
130175
}
131176

177+
/// Returns the list of languages to use for CodeQL analysis.
132178
pub fn languages(&self) -> Vec<CodeQLLanguage> {
133-
self.languages
179+
log::debug!("Getting languages for analysis: {:?}", self.languages);
180+
let languages = self
181+
.languages
134182
.iter()
135183
.map(|lang| CodeQLLanguage::from(lang.as_str()))
136-
.collect()
184+
.collect();
185+
log::debug!("Converted to CodeQL languages: {:?}", languages);
186+
languages
187+
}
188+
189+
/// Gets the possible directories for CodeQL operations.
190+
///
191+
/// This function identifies potential locations for CodeQL operation directories in the following order:
192+
/// 1. The `.codeql` directory in the GitHub workspace (if running in GitHub Actions)
193+
/// 2. The `.codeql` directory in the current working directory
194+
/// 3. The `.codeql` directory in the GitHub Actions runner's temp directory (if available)
195+
/// 4. The `.codeql` directory in the system's temporary directory
196+
///
197+
/// Each path is checked for existence and created if necessary by the caller.
198+
///
199+
/// # Returns
200+
/// - `Result<Vec<PathBuf>>`: A vector of possible directory paths for CodeQL operations
201+
///
202+
/// # Errors
203+
/// - If `working_directory()` fails
204+
/// - If path canonicalization fails
205+
fn get_codeql_directories(&self) -> Result<Vec<PathBuf>> {
206+
let mut paths = Vec::new();
207+
208+
// GITHUB_WORKSPACE
209+
if let Ok(github_workspace) = std::env::var("GITHUB_WORKSPACE") {
210+
paths.push(PathBuf::from(github_workspace).join(".codeql"));
211+
}
212+
213+
// Local CodeQL directory in the working directory
214+
if let Ok(local_codeql) = self.working_directory()?.join(".codeql").canonicalize() {
215+
paths.push(local_codeql);
216+
}
217+
218+
// Runner temp directory
219+
if let Ok(runner_temp) = std::env::var("RUNNER_TEMP") {
220+
paths.push(PathBuf::from(runner_temp).join(".codeql").canonicalize()?);
221+
}
222+
// temp_dir
223+
if let Ok(temp_dir) = std::env::temp_dir().canonicalize() {
224+
paths.push(temp_dir.join(".codeql"));
225+
}
226+
227+
Ok(paths)
137228
}
138229

230+
/// Returns the directory to use for CodeQL operations.
231+
///
139232
/// Gets the CodeQL directory to use for the action. It will first check if a local
140233
/// `.codeql` directory exists in the working directory parent. If not, it will
141234
/// use the `RUNNER_TEMP` directory. If neither exists, it will create a new
@@ -144,17 +237,8 @@ impl Action {
144237
/// It uses the parent of the working directory to to stop issues where the
145238
/// database/sarif files gets indexed by CodeQL.
146239
pub fn get_codeql_dir(&self) -> Result<PathBuf> {
147-
let paths = vec![
148-
// Local CodeQL directory in the working directory parent
149-
self.working_directory()?
150-
.join("..")
151-
.join(".codeql")
152-
.canonicalize()?,
153-
// Runner temp directory
154-
PathBuf::from(std::env::var("RUNNER_TEMP").unwrap_or_else(|_| "/tmp".to_string()))
155-
.join(".codeql")
156-
.canonicalize()?,
157-
];
240+
let paths = self.get_codeql_directories()?;
241+
log::debug!("Possible CodeQL directories: {:?}", paths);
158242

159243
for path in paths {
160244
if !path.exists() {
@@ -173,6 +257,11 @@ impl Action {
173257
Err(anyhow::anyhow!("Failed to create CodeQL directory",))
174258
}
175259

260+
/// Validates the provided languages against the supported CodeQL languages.
261+
///
262+
/// # Errors
263+
///
264+
/// Returns an error if any of the provided languages are not supported.
176265
pub fn validate_languages(&self, codeql_languages: &Vec<CodeQLLanguage>) -> Result<()> {
177266
for lang in self.languages() {
178267
let mut supported = false;
@@ -198,6 +287,9 @@ impl Action {
198287
Ok(())
199288
}
200289

290+
/// Returns the CodeQL version to use.
291+
///
292+
/// If the CodeQL version is not provided, it defaults to "latest".
201293
pub fn codeql_version(&self) -> &str {
202294
if self.codeql_version.is_empty() {
203295
log::debug!("No CodeQL version provided, using the latest version");
@@ -206,6 +298,11 @@ impl Action {
206298
&self.codeql_version
207299
}
208300

301+
/// Installs the specified CodeQL packs.
302+
///
303+
/// # Errors
304+
///
305+
/// Returns an error if any of the packs cannot be installed.
209306
pub async fn install_packs(&self, codeql: &CodeQL) -> Result<()> {
210307
log::info!("Installing CodeQL Packs");
211308
for pack in &self.packs {
@@ -238,11 +335,15 @@ impl Action {
238335
Ok(())
239336
}
240337

338+
/// Returns whether attestation is enabled.
241339
pub fn attestation(&self) -> bool {
340+
log::debug!("Attestation enabled: {}", self.attestation);
242341
self.attestation
243342
}
244343

344+
/// Returns whether empty databases are allowed.
245345
pub fn allow_empty_database(&self) -> bool {
346+
log::debug!("Allow empty database: {}", self.allow_empty_database);
246347
self.allow_empty_database
247348
}
248349
}
@@ -251,6 +352,12 @@ impl Action {
251352
mod tests {
252353
use super::*;
253354

355+
/// Helper function to create a test Action instance with predefined values
356+
///
357+
/// Creates an Action with:
358+
/// - A single extractor repository "owner/repo"
359+
/// - A single language "iac"
360+
/// - Default values for all other fields
254361
fn action() -> Action {
255362
Action {
256363
extractors: vec!["owner/repo".to_string()],
@@ -259,6 +366,11 @@ mod tests {
259366
}
260367
}
261368

369+
/// Test that language validation works correctly
370+
///
371+
/// Tests two scenarios:
372+
/// 1. When a language is specified that isn't supported by CodeQL (should error)
373+
/// 2. When a language is specified that is supported by CodeQL (should pass)
262374
#[test]
263375
fn test_validate_languages() {
264376
let action = action();

0 commit comments

Comments
 (0)