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.
26use std:: path:: PathBuf ;
37
48use anyhow:: { Context , Result } ;
59use ghactions:: prelude:: * ;
610use ghactions_core:: repository:: reference:: RepositoryReference as Repository ;
711use ghastoolkit:: { CodeQL , CodeQLPack , codeql:: CodeQLLanguage } ;
812
13+ /// ASCII art banner for the CodeQL Extractor Action
914pub const BANNER : & str = r#" ___ _ ____ __ __ _ _ _
1015 / __\___ __| | ___ /___ \/ / /__\_ _| |_ /_\ ___| |_
1116 / / / _ \ / _` |/ _ \// / / / /_\ \ \/ / __|//_\\ / __| __|
1217/ /__| (_) | (_| | __/ \_/ / /___//__ > <| |_/ _ \ (__| |_
1318\____/\___/ \__,_|\___\___,_\____/\__/ /_/\_\\__\_/ \_/\___|\__|"# ;
19+
20+ /// Version of the CodeQL Extractor Action, pulled from Cargo.toml
1421pub const VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
22+
23+ /// Authors of the CodeQL Extractor Action, pulled from Cargo.toml
1524pub 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
96105impl 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 {
251352mod 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