146 lines
4.5 KiB
Rust
146 lines
4.5 KiB
Rust
//! Attune Timer Sensor
|
|
//!
|
|
//! A standalone sensor daemon that monitors timer-based triggers and emits events
|
|
//! to the Attune platform. Each timer sensor instance manages multiple timer schedules
|
|
//! based on active rules.
|
|
//!
|
|
//! Configuration is provided via environment variables or stdin JSON:
|
|
//! - ATTUNE_API_URL: Base URL of the Attune API
|
|
//! - ATTUNE_API_TOKEN: Service account token for authentication
|
|
//! - ATTUNE_SENSOR_REF: Reference name for this sensor (e.g., "core.timer")
|
|
//! - ATTUNE_MQ_URL: RabbitMQ connection URL
|
|
//! - ATTUNE_MQ_EXCHANGE: RabbitMQ exchange name (default: "attune")
|
|
//! - ATTUNE_LOG_LEVEL: Logging verbosity (default: "info")
|
|
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use tracing::{error, info};
|
|
|
|
mod api_client;
|
|
mod config;
|
|
mod rule_listener;
|
|
mod timer_manager;
|
|
mod token_refresh;
|
|
mod types;
|
|
|
|
use config::SensorConfig;
|
|
use rule_listener::RuleLifecycleListener;
|
|
use timer_manager::TimerManager;
|
|
use token_refresh::TokenRefreshManager;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "attune-core-timer-sensor")]
|
|
#[command(about = "Standalone timer sensor for Attune automation platform", long_about = None)]
|
|
struct Args {
|
|
/// Log level (trace, debug, info, warn, error)
|
|
#[arg(short, long, default_value = "info")]
|
|
log_level: String,
|
|
|
|
/// Read configuration from stdin as JSON instead of environment variables
|
|
#[arg(long)]
|
|
stdin_config: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let args = Args::parse();
|
|
|
|
// Initialize tracing
|
|
let log_level = args.log_level.parse().unwrap_or(tracing::Level::INFO);
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_max_level(log_level)
|
|
.with_target(false)
|
|
.with_thread_ids(true)
|
|
.json()
|
|
.init();
|
|
|
|
info!("Starting Attune Timer Sensor");
|
|
info!("Version: {}", env!("CARGO_PKG_VERSION"));
|
|
|
|
// Load configuration
|
|
let config = if args.stdin_config {
|
|
info!("Reading configuration from stdin");
|
|
SensorConfig::from_stdin().await?
|
|
} else {
|
|
info!("Reading configuration from environment variables");
|
|
SensorConfig::from_env()?
|
|
};
|
|
|
|
config.validate()?;
|
|
info!(
|
|
"Configuration loaded successfully: sensor_ref={}, api_url={}",
|
|
config.sensor_ref, config.api_url
|
|
);
|
|
|
|
// Create API client
|
|
let api_client = api_client::ApiClient::new(config.api_url.clone(), config.api_token.clone());
|
|
|
|
// Verify API connectivity
|
|
info!("Verifying API connectivity...");
|
|
api_client
|
|
.health_check()
|
|
.await
|
|
.context("Failed to connect to Attune API")?;
|
|
info!("API connectivity verified");
|
|
|
|
// Create timer manager
|
|
let timer_manager = TimerManager::new(api_client.clone())
|
|
.await
|
|
.context("Failed to initialize timer manager")?;
|
|
info!("Timer manager initialized");
|
|
|
|
// Create rule lifecycle listener
|
|
let listener = RuleLifecycleListener::new(
|
|
config.mq_url.clone(),
|
|
config.mq_exchange.clone(),
|
|
config.sensor_ref.clone(),
|
|
api_client.clone(),
|
|
timer_manager.clone(),
|
|
);
|
|
|
|
info!("Rule lifecycle listener initialized");
|
|
|
|
// Start token refresh manager (auto-refresh when 80% of TTL elapsed)
|
|
let refresh_manager = TokenRefreshManager::new(api_client.clone(), 0.8);
|
|
let _refresh_handle = refresh_manager.start();
|
|
info!("Token refresh manager started (will refresh at 80% of TTL)");
|
|
|
|
// Set up graceful shutdown handler
|
|
let timer_manager_clone = timer_manager.clone();
|
|
let shutdown_signal = tokio::spawn(async move {
|
|
match tokio::signal::ctrl_c().await {
|
|
Ok(()) => {
|
|
info!("Shutdown signal received");
|
|
if let Err(e) = timer_manager_clone.shutdown().await {
|
|
error!("Error during timer manager shutdown: {}", e);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to listen for shutdown signal: {}", e);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Start the listener (this will block until stopped)
|
|
info!("Starting rule lifecycle listener...");
|
|
match listener.start().await {
|
|
Ok(()) => {
|
|
info!("Rule lifecycle listener stopped gracefully");
|
|
}
|
|
Err(e) => {
|
|
error!("Rule lifecycle listener error: {}", e);
|
|
return Err(e);
|
|
}
|
|
}
|
|
|
|
// Wait for shutdown to complete
|
|
let _ = shutdown_signal.await;
|
|
|
|
// Ensure timer manager is fully shutdown
|
|
timer_manager.shutdown().await?;
|
|
|
|
info!("Timer sensor has shut down gracefully");
|
|
Ok(())
|
|
}
|