refactor: rip out the legacy TrackedRepository / repositories path (#161)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 8m20s
CI / Deploy Dashboard (push) Successful in 3m13s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2m7s
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 8m20s
CI / Deploy Dashboard (push) Successful in 3m13s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2m7s
This commit was merged in pull request #161.
This commit is contained in:
@@ -16,6 +16,7 @@ use crate::pipeline::gitleaks::GitleaksScanner;
|
||||
use crate::pipeline::lint::LintScanner;
|
||||
use crate::pipeline::patterns::{GdprPatternScanner, OAuthPatternScanner};
|
||||
use crate::pipeline::plan::build_scan_plan;
|
||||
use crate::pipeline::repo_view::RepoView;
|
||||
use crate::pipeline::sbom::SbomScanner;
|
||||
use crate::pipeline::semgrep::SemgrepScanner;
|
||||
|
||||
@@ -51,72 +52,8 @@ impl PipelineOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(repo_id = %repo_id, trigger = ?trigger))]
|
||||
pub async fn run(&self, repo_id: &str, trigger: ScanTrigger) -> Result<(), AgentError> {
|
||||
// Look up the repository
|
||||
let repo = self
|
||||
.db
|
||||
.repositories()
|
||||
.find_one(doc! { "_id": mongodb::bson::oid::ObjectId::parse_str(repo_id).map_err(|e| AgentError::Other(e.to_string()))? })
|
||||
.await?
|
||||
.ok_or_else(|| AgentError::Other(format!("Repository {repo_id} not found")))?;
|
||||
|
||||
// Create scan run
|
||||
let scan_run = ScanRun::new(repo_id.to_string(), trigger);
|
||||
let insert = self.db.scan_runs().insert_one(&scan_run).await?;
|
||||
let scan_run_id = insert
|
||||
.inserted_id
|
||||
.as_object_id()
|
||||
.map(|id| id.to_hex())
|
||||
.unwrap_or_default();
|
||||
|
||||
let result = self.run_pipeline(&repo, &scan_run_id).await;
|
||||
|
||||
// Update scan run status
|
||||
match &result {
|
||||
Ok(count) => {
|
||||
self.db
|
||||
.scan_runs()
|
||||
.update_one(
|
||||
doc! { "_id": &insert.inserted_id },
|
||||
doc! {
|
||||
"$set": {
|
||||
"status": "completed",
|
||||
"current_phase": "completed",
|
||||
"new_findings_count": *count as i64,
|
||||
"completed_at": mongodb::bson::DateTime::now(),
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(repo_id, error = %e, "Scan pipeline failed");
|
||||
self.db
|
||||
.scan_runs()
|
||||
.update_one(
|
||||
doc! { "_id": &insert.inserted_id },
|
||||
doc! {
|
||||
"$set": {
|
||||
"status": "failed",
|
||||
"error_message": e.to_string(),
|
||||
"completed_at": mongodb::bson::DateTime::now(),
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(repo_id = repo.name.as_str()))]
|
||||
async fn run_pipeline(
|
||||
&self,
|
||||
repo: &TrackedRepository,
|
||||
scan_run_id: &str,
|
||||
) -> Result<u32, AgentError> {
|
||||
async fn run_pipeline(&self, repo: &RepoView, scan_run_id: &str) -> Result<u32, AgentError> {
|
||||
let repo_id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
|
||||
|
||||
// Stage 0: Change detection
|
||||
@@ -130,7 +67,6 @@ impl PipelineOrchestrator {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let current_sha = GitOps::get_head_sha(&repo_path)?;
|
||||
let mut all_findings: Vec<Finding> = Vec::new();
|
||||
|
||||
// Stage 1: Semgrep SAST
|
||||
@@ -396,20 +332,9 @@ impl PipelineOrchestrator {
|
||||
tracing::warn!("[{repo_id}] Issue creation failed: {e}");
|
||||
}
|
||||
|
||||
// Stage 7: Update repository
|
||||
self.db
|
||||
.repositories()
|
||||
.update_one(
|
||||
doc! { "_id": repo.id },
|
||||
doc! {
|
||||
"$set": {
|
||||
"last_scanned_commit": ¤t_sha,
|
||||
"updated_at": mongodb::bson::DateTime::now(),
|
||||
},
|
||||
"$inc": { "findings_count": new_count as i64 },
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
// The onboarded target's findings_count and the git artifact's
|
||||
// last_scanned_commit watermark are persisted by `finalize_target` after
|
||||
// `run_pipeline` returns.
|
||||
|
||||
// Stage 8: DAST (async, optional — only if a DastTarget is configured)
|
||||
tracing::info!("[{repo_id}] Stage 8: Checking for DAST targets");
|
||||
@@ -526,7 +451,7 @@ impl PipelineOrchestrator {
|
||||
|
||||
match target.code_artifact() {
|
||||
Some(code) if code.kind == ArtifactKind::GitRepo => {
|
||||
let repo = repo_view_from_target(target, code);
|
||||
let repo = RepoView::from_target(target, code);
|
||||
let new_count = self.run_pipeline(&repo, scan_run_id).await?;
|
||||
self.finalize_target(target, &repo, new_count).await?;
|
||||
Ok(new_count)
|
||||
@@ -703,7 +628,7 @@ impl PipelineOrchestrator {
|
||||
async fn finalize_target(
|
||||
&self,
|
||||
target: &OnboardedTarget,
|
||||
repo: &TrackedRepository,
|
||||
repo: &RepoView,
|
||||
new_count: u32,
|
||||
) -> Result<(), AgentError> {
|
||||
let oid = match target.id {
|
||||
@@ -751,37 +676,6 @@ impl PipelineOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a legacy `TrackedRepository` view from an onboarded target's code
|
||||
/// artifact, so the unified pipeline can reuse the existing repo pipeline. The
|
||||
/// inverse of the migration's `repo_to_target`. `_id` is preserved so findings
|
||||
/// and DAST lookups resolve against the same key.
|
||||
fn repo_view_from_target(target: &OnboardedTarget, code: &Artifact) -> TrackedRepository {
|
||||
let mut repo = TrackedRepository::new(target.name.clone(), code.source_ref.clone());
|
||||
repo.id = target.id;
|
||||
if let Some(git) = &code.git {
|
||||
repo.default_branch = git.default_branch.clone();
|
||||
repo.last_scanned_commit = git.last_scanned_commit.clone();
|
||||
repo.local_path = git.local_path.clone();
|
||||
}
|
||||
if let Some(auth) = &code.auth {
|
||||
repo.auth_token = auth.secret.clone();
|
||||
repo.auth_username = auth.username.clone();
|
||||
}
|
||||
if let Some(it) = &target.scan_config.issue_tracker {
|
||||
repo.tracker_type = it.tracker_type.clone();
|
||||
repo.tracker_owner = it.owner.clone();
|
||||
repo.tracker_repo = it.repo.clone();
|
||||
repo.tracker_token = it.token.clone();
|
||||
}
|
||||
repo.scan_schedule = target.scan_schedule.clone();
|
||||
repo.webhook_enabled = target.webhook_enabled;
|
||||
repo.webhook_secret = target.webhook_secret.clone();
|
||||
repo.findings_count = target.findings_count;
|
||||
repo.created_at = target.created_at;
|
||||
repo.updated_at = target.updated_at;
|
||||
repo
|
||||
}
|
||||
|
||||
/// Extract the scheme + host from a git URL.
|
||||
/// e.g. "https://gitea.example.com/owner/repo.git" -> "https://gitea.example.com"
|
||||
/// e.g. "ssh://git@gitea.example.com:22/owner/repo.git" -> "https://gitea.example.com"
|
||||
@@ -840,7 +734,7 @@ mod tests {
|
||||
target.artifacts.push(artifact);
|
||||
|
||||
let code = target.code_artifact().expect("code artifact");
|
||||
let repo = repo_view_from_target(&target, code);
|
||||
let repo = RepoView::from_target(&target, code);
|
||||
|
||||
assert_eq!(repo.id, target.id); // preserved
|
||||
assert_eq!(repo.git_url, "https://git/acme.git");
|
||||
|
||||
Reference in New Issue
Block a user