feat(plc): ingest CODESYS projects from a git repo (SAST + SBOM)
CI / Check (pull_request) Successful in 5m47s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

Onboard a PLC/SPS target with a git repo (or source archive) of exported control
logic and get the same results as an upload — the natural way CODESYS projects are
version-controlled, so each scan is a git pull rather than a blob re-upload.

- scan_matrix: the PLC control-logic requirement is satisfied by a PlcProject
  *or* a code artifact (git repo / source archive).
- plan: resolve_artifact binds the PLC scan to the PlcProject if present, else the
  code artifact.
- orchestrator: a PLC/SPS target routes to the control-logic scanner over the
  clone (not the SAST/semgrep pipeline), then still runs DAST for a reachable
  device.
- plc::sbom::collect_sbom: the control-app SBOM now also comes from any
  `.projectarchive` committed inside the working tree (a git repo / extracted
  archive), in addition to an uploaded archive.

Docs: new guide page "PLC / SPS (CODESYS)" documenting the best-case git repo
layout (commit PLCopen XML exports for SAST + the .projectarchive for the SBOM;
don't commit only the binary .project). UI: onboarding wizard shows the same
guidance for PLC/SPS targets.

Implements the git-ingest follow-up from #166 / #165. Tracker #167.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-07-16 17:40:54 +02:00
co-authored by Claude Opus 4.8
parent 7369e031c4
commit 1a8a6e5149
7 changed files with 241 additions and 25 deletions
+19 -1
View File
@@ -294,7 +294,12 @@ fn requirement_satisfied(req: ArtifactRequirement, target: &OnboardedTarget) ->
ArtifactRequirement::Code => target.code_artifact().is_some(),
ArtifactRequirement::RunningUrl => target.has(ArtifactKind::LiveUrl),
ArtifactRequirement::Firmware => target.has(ArtifactKind::FirmwareImage),
ArtifactRequirement::Plc => target.has(ArtifactKind::PlcProject),
// A PLC project artifact, or a code artifact (git repo / source archive)
// holding the control logic as PLCopen XML / ST exports — the common way
// CODESYS projects are version-controlled.
ArtifactRequirement::Plc => {
target.has(ArtifactKind::PlcProject) || target.code_artifact().is_some()
}
ArtifactRequirement::Mobile => target.has(ArtifactKind::MobilePackage),
ArtifactRequirement::Container => target.has(ArtifactKind::ContainerImage),
ArtifactRequirement::Any => true,
@@ -406,6 +411,19 @@ mod tests {
assert!(dast.blocked_reason.is_some());
}
#[test]
fn plc_control_logic_is_satisfied_by_a_git_repo() {
// A CODESYS project version-controlled in git (PLCopen XML / ST exports),
// no uploaded PlcProject artifact.
let t = target_with(TargetType::PlcSps, vec![Artifact::git_repo("u", "main")]);
let opts = applicable_scans(&t);
let plc = option(&opts, ScanType::PlcControlLogic).expect("control-logic offered");
assert!(
plc.default_on && plc.blocked_reason.is_none(),
"a git repo should satisfy PLC control-logic"
);
}
#[test]
fn plc_composite_lights_up_device_scans_with_firmware_and_url() {
// A CODESYS-on-Yocto device: PLC project + firmware image + WebVisu URL.