sensors using keys

This commit is contained in:
2026-02-20 14:11:06 -06:00
parent f9cfcf8f40
commit a84c07082c
9 changed files with 416 additions and 260 deletions

View File

@@ -23,7 +23,7 @@ use crate::repositories::runtime::{CreateRuntimeInput, RuntimeRepository};
use crate::repositories::trigger::{
CreateSensorInput, CreateTriggerInput, SensorRepository, TriggerRepository,
};
use crate::repositories::{Create, FindByRef};
use crate::repositories::{Create, FindById, FindByRef, Update};
/// Result of loading pack components into the database.
#[derive(Debug, Default)]
@@ -514,6 +514,47 @@ impl<'a> PackComponentLoader<'a> {
.unwrap_or("native");
let (sensor_runtime_id, sensor_runtime_ref) = self.resolve_runtime(runner_type).await?;
// Validate: if the runner_type suggests an interpreted runtime (not native)
// but we couldn't resolve it, or it resolved to a runtime with no
// execution_config, warn at registration time rather than failing
// opaquely at sensor startup with "Permission denied".
let is_native_runner = matches!(
runner_type.to_lowercase().as_str(),
"native" | "builtin" | "standalone"
);
if sensor_runtime_id == 0 && !is_native_runner {
let msg = format!(
"Sensor '{}' declares runner_type '{}' but no matching runtime \
was found in the database. The sensor will not be able to start. \
Ensure the core pack (with runtimes) is loaded before registering \
packs that depend on its runtimes.",
filename, runner_type
);
warn!("{}", msg);
result.warnings.push(msg);
} else if sensor_runtime_id != 0 && !is_native_runner {
// Verify the resolved runtime has a non-empty execution_config
if let Some(runtime) =
RuntimeRepository::find_by_id(self.pool, sensor_runtime_id).await?
{
let exec_config = runtime.parsed_execution_config();
if exec_config.interpreter.binary.is_empty()
|| exec_config.interpreter.binary == "native"
|| exec_config.interpreter.binary == "none"
{
let msg = format!(
"Sensor '{}' declares runner_type '{}' (resolved to runtime '{}') \
but that runtime has no interpreter configured in its \
execution_config. The sensor will fail to start. \
Check the runtime definition for '{}'.",
filename, runner_type, runtime.r#ref, runtime.r#ref
);
warn!("{}", msg);
result.warnings.push(msg);
}
}
}
let sensor_ref = match data.get("ref").and_then(|v| v.as_str()) {
Some(r) => r.to_string(),
None => {
@@ -524,16 +565,6 @@ impl<'a> PackComponentLoader<'a> {
}
};
// Check if sensor already exists
if let Some(existing) = SensorRepository::find_by_ref(self.pool, &sensor_ref).await? {
info!(
"Sensor '{}' already exists (ID: {}), skipping",
sensor_ref, existing.id
);
result.sensors_skipped += 1;
continue;
}
let name = extract_name_from_ref(&sensor_ref);
let label = data
.get("label")
@@ -570,6 +601,41 @@ impl<'a> PackComponentLoader<'a> {
.and_then(|v| serde_json::to_value(v).ok())
.unwrap_or_else(|| serde_json::json!({}));
// Upsert: update existing sensors so re-registration corrects
// stale metadata (especially runtime assignments).
if let Some(existing) = SensorRepository::find_by_ref(self.pool, &sensor_ref).await? {
use crate::repositories::trigger::UpdateSensorInput;
let update_input = UpdateSensorInput {
label: Some(label),
description: Some(description),
entrypoint: Some(entrypoint),
runtime: Some(sensor_runtime_id),
runtime_ref: Some(sensor_runtime_ref.clone()),
trigger: Some(trigger_id.unwrap_or(existing.trigger)),
trigger_ref: Some(trigger_ref.unwrap_or(existing.trigger_ref.clone())),
enabled: Some(enabled),
param_schema,
config: Some(config),
};
match SensorRepository::update(self.pool, existing.id, update_input).await {
Ok(_) => {
info!(
"Updated sensor '{}' (ID: {}, runtime: {} → {})",
sensor_ref, existing.id, existing.runtime_ref, sensor_runtime_ref
);
result.sensors_loaded += 1;
}
Err(e) => {
let msg = format!("Failed to update sensor '{}': {}", sensor_ref, e);
warn!("{}", msg);
result.warnings.push(msg);
}
}
continue;
}
let input = CreateSensorInput {
r#ref: sensor_ref.clone(),
pack: Some(self.pack_id),

View File

@@ -531,8 +531,13 @@ pub struct UpdateSensorInput {
pub label: Option<String>,
pub description: Option<String>,
pub entrypoint: Option<String>,
pub runtime: Option<Id>,
pub runtime_ref: Option<String>,
pub trigger: Option<Id>,
pub trigger_ref: Option<String>,
pub enabled: Option<bool>,
pub param_schema: Option<JsonSchema>,
pub config: Option<JsonValue>,
}
#[async_trait::async_trait]
@@ -688,6 +693,42 @@ impl Update for SensorRepository {
has_updates = true;
}
if let Some(runtime) = input.runtime {
if has_updates {
query.push(", ");
}
query.push("runtime = ");
query.push_bind(runtime);
has_updates = true;
}
if let Some(runtime_ref) = &input.runtime_ref {
if has_updates {
query.push(", ");
}
query.push("runtime_ref = ");
query.push_bind(runtime_ref);
has_updates = true;
}
if let Some(trigger) = input.trigger {
if has_updates {
query.push(", ");
}
query.push("trigger = ");
query.push_bind(trigger);
has_updates = true;
}
if let Some(trigger_ref) = &input.trigger_ref {
if has_updates {
query.push(", ");
}
query.push("trigger_ref = ");
query.push_bind(trigger_ref);
has_updates = true;
}
if let Some(param_schema) = &input.param_schema {
if has_updates {
query.push(", ");
@@ -697,6 +738,15 @@ impl Update for SensorRepository {
has_updates = true;
}
if let Some(config) = &input.config {
if has_updates {
query.push(", ");
}
query.push("config = ");
query.push_bind(config);
has_updates = true;
}
if !has_updates {
// No updates requested, fetch and return existing entity
return Self::get_by_id(executor, id).await;