quickseal 0.1.4

Create Kubernetes SealedSecrets with a single secure command
Documentation
//! Signal handler registration and cleanup coordination

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

/// Global flag for coordinating cleanup across threads
static CLEANUP_REQUESTED: AtomicBool = AtomicBool::new(false);

/// Check if cleanup has been requested (e.g., via signal)
pub fn is_cleanup_requested() -> bool {
    CLEANUP_REQUESTED.load(Ordering::SeqCst)
}

/// Request cleanup (sets the global flag)
pub fn request_cleanup() {
    CLEANUP_REQUESTED.store(true, Ordering::SeqCst);
}

/// Register signal handlers for graceful shutdown
/// Handles SIGINT (Ctrl+C), SIGTERM, and SIGHUP on Unix
pub fn register_signal_handlers() -> anyhow::Result<()> {
    // Register SIGINT handler (Ctrl+C) - works on all platforms
    ctrlc::set_handler(move || {
        eprintln!("\nReceived interrupt signal, cleaning up...");
        request_cleanup();
        std::process::exit(5); // Exit code 5 for signal received
    })
    .map_err(|e| anyhow::anyhow!("Failed to set Ctrl+C handler: {}", e))?;

    // Register additional Unix signals (SIGTERM, SIGHUP)
    #[cfg(unix)]
    {
        use signal_hook::consts::signal::*;
        use signal_hook::flag;

        let cleanup_flag = Arc::new(AtomicBool::new(false));

        // SIGTERM
        flag::register(SIGTERM, Arc::clone(&cleanup_flag))
            .map_err(|e| anyhow::anyhow!("Failed to register SIGTERM handler: {}", e))?;

        // SIGHUP
        flag::register(SIGHUP, Arc::clone(&cleanup_flag))
            .map_err(|e| anyhow::anyhow!("Failed to register SIGHUP handler: {}", e))?;

        // Spawn thread to monitor signals
        std::thread::spawn(move || loop {
            if cleanup_flag.load(Ordering::Relaxed) {
                eprintln!("\nReceived termination signal, cleaning up...");
                request_cleanup();
                std::process::exit(5);
            }
            std::thread::sleep(std::time::Duration::from_millis(100));
        });
    }

    Ok(())
}

/// Cleanup guard that ensures cleanup runs on drop
pub struct CleanupGuard {
    cleanup_fn: Option<Box<dyn FnOnce() + Send>>,
}

impl CleanupGuard {
    /// Create a new cleanup guard with a cleanup function
    pub fn new<F>(cleanup_fn: F) -> Self
    where
        F: FnOnce() + Send + 'static,
    {
        Self {
            cleanup_fn: Some(Box::new(cleanup_fn)),
        }
    }

    /// Explicitly run cleanup (also runs automatically on drop)
    pub fn cleanup(mut self) {
        if let Some(cleanup_fn) = self.cleanup_fn.take() {
            cleanup_fn();
        }
    }
}

impl Drop for CleanupGuard {
    fn drop(&mut self) {
        if let Some(cleanup_fn) = self.cleanup_fn.take() {
            cleanup_fn();
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cleanup_flag() {
        assert!(!is_cleanup_requested());
        request_cleanup();
        assert!(is_cleanup_requested());
    }

    #[test]
    fn test_cleanup_guard() {
        use std::sync::Arc;
        let cleaned = Arc::new(AtomicBool::new(false));
        let cleaned_clone = Arc::clone(&cleaned);

        {
            let _guard = CleanupGuard::new(move || {
                cleaned_clone.store(true, Ordering::SeqCst);
            });
            assert!(!cleaned.load(Ordering::SeqCst));
        }

        assert!(cleaned.load(Ordering::SeqCst));
    }
}