feat(plc): ephemeral soft-PLC provisioning + program load (#183) #193

Merged
sharang merged 3 commits from feat/plc-provision-and-test into main 2026-07-17 07:47:44 +00:00
3 changed files with 24 additions and 10 deletions
Showing only changes of commit 896a06e8f6 - Show all commits
@@ -584,11 +584,16 @@ impl PipelineOrchestrator {
)
})
.find_map(|a| {
let path = ingest_set.get(&a.id).and_then(|ia| ia.working_path.clone())?;
let path = ingest_set
.get(&a.id)
.and_then(|ia| ia.working_path.clone())?;
crate::pipeline::plc::runtime::extract_program(&path)
});
let Some(program) = program else {
tracing::info!(target_id, "provision-and-test: no loadable control-logic program");
tracing::info!(
target_id,
"provision-and-test: no loadable control-logic program"
);
return Ok(0);
};
@@ -316,7 +316,10 @@ mod tests {
)
.expect("w");
let prog = extract_program(&s.0).expect("program");
assert_eq!(prog.file_name, "big.st", "largest ST wins when none complete");
assert_eq!(
prog.file_name, "big.st",
"largest ST wins when none complete"
);
// Only a PLCopen XML present.
let s2 = Scratch::new();
@@ -48,10 +48,8 @@ pub trait SoftPlc {
) -> impl std::future::Future<Output = Result<ProvisionedRuntime, AgentError>> + Send;
/// Tear an instance down. Best-effort and idempotent — never fails the scan.
fn teardown(
&self,
handle: &ProvisionedRuntime,
) -> impl std::future::Future<Output = ()> + Send;
fn teardown(&self, handle: &ProvisionedRuntime)
-> impl std::future::Future<Output = ()> + Send;
}
/// Provisions OpenPLC instances by shelling out to the Docker CLI.
@@ -99,7 +97,9 @@ impl SoftPlc for DockerSoftPlc {
"soft-PLC teardown non-zero exit: {}",
String::from_utf8_lossy(&out.stderr).trim()
),
Err(e) => tracing::warn!(instance = %handle.name, error = %e, "soft-PLC teardown failed"),
Err(e) => {
tracing::warn!(instance = %handle.name, error = %e, "soft-PLC teardown failed")
}
}
}
}
@@ -277,13 +277,19 @@ mod tests {
assert_eq!(args[cpu + 1], "0.5");
assert!(args.iter().any(|a| a == "--pids-limit"));
// Hardening.
let so = args.iter().position(|a| a == "--security-opt").expect("secopt");
let so = args
.iter()
.position(|a| a == "--security-opt")
.expect("secopt");
assert_eq!(args[so + 1], "no-new-privileges");
// Ownership + target labels for reaping / attribution.
assert!(args.contains(&"certifai.ephemeral=plc-runtime".to_string()));
assert!(args.contains(&"certifai.target=target-123".to_string()));
// Image is last.
assert_eq!(args.last().map(String::as_str), Some("registry.example.com/openplc:latest"));
assert_eq!(
args.last().map(String::as_str),
Some("registry.example.com/openplc:latest")
);
}
#[test]