use crate::error::Result;
use crate::models::{Issue, Pipeline, Severity};
use crate::parsers::gitlab::parse_includes;
use std::path::Path;
pub fn audit(pipeline: &Pipeline) -> Result<Vec<Issue>> {
let mut issues = Vec::new();
if pipeline.provider != crate::models::Provider::GitLabCI {
return Ok(issues);
}
let include_info = match parse_includes(&pipeline.source) {
Ok(info) => info,
Err(_) => return Ok(issues), };
let source_path = Path::new(&pipeline.source);
for local in &include_info.local {
let path = if local.starts_with("./") || local.starts_with("../") {
if let Some(parent) = source_path.parent() {
parent.join(local)
} else {
Path::new(local).to_path_buf()
}
} else {
Path::new(local).to_path_buf()
};
if !path.exists() {
let (line, _) = pipeline.find_line("include:");
issues.push(Issue::for_job(
Severity::Warning,
&format!("Local include not found: {}", local),
"",
line,
1,
Some("Ensure the file exists or check the path".to_string()),
));
}
}
for remote in &include_info.remote {
let (line, _) = pipeline.find_line("include:");
issues.push(Issue::for_job(
Severity::Info,
&format!("Remote include: {}", remote),
"",
line,
1,
Some("Remote includes require network access - add allow_failure: true for resilient pipelines".to_string()),
));
}
for project in &include_info.project {
let (line, _) = pipeline.find_line("include:");
issues.push(Issue::for_job(
Severity::Info,
&format!("Project include: {}", project),
"",
line,
1,
Some(
"Project includes depend on external repository - ensure it's accessible"
.to_string(),
),
));
}
Ok(issues)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_include_block() {
let pipeline = Pipeline {
provider: crate::models::Provider::GitLabCI,
jobs: vec![],
env: vec![],
source: "stages: [build]\nbuild: script: echo hi\n".to_string(),
};
let issues = audit(&pipeline).unwrap();
assert!(issues.is_empty());
}
#[test]
fn test_local_include_not_applicable_to_github() {
let pipeline = Pipeline {
provider: crate::models::Provider::GitHubActions,
jobs: vec![],
env: vec![],
source: "jobs:\n build:\n runs-on: ubuntu\n".to_string(),
};
let issues = audit(&pipeline).unwrap();
assert!(issues.is_empty());
}
}