quickseal 0.1.4

Create Kubernetes SealedSecrets with a single secure command
Documentation
//! CLI command implementations

use crate::cli::args::CreateCommand;
use crate::k8s;
use crate::security;

/// Execute the create command
pub fn create_command(cmd: &CreateCommand) -> Result<(), anyhow::Error> {
    // Register signal handlers for graceful cleanup
    security::register_signal_handlers()?;

    // Validate arguments
    cmd.validate()?;

    // Get SecretRequest from interactive mode or CLI args
    let request = if cmd.interactive {
        crate::cli::interactive_create()?
    } else {
        cmd.to_secret_request()?
    };

    // Display cluster context (User Story 2 - Safety feature)
    match k8s::load_context_sync() {
        Ok(context) => {
            eprintln!("\n{}", context.display());
            eprintln!();
        }
        Err(e) => {
            eprintln!("\n⚠️  Warning: Could not load cluster context");
            eprintln!("   {}", e);
            eprintln!();
            eprintln!("   Possible causes:");
            eprintln!("   - No kubeconfig file found (~/.kube/config or $KUBECONFIG)");
            eprintln!("   - Invalid kubeconfig format");
            eprintln!("   - No current context configured");
            eprintln!();
            eprintln!("   You can still create the sealed secret, but verify you're");
            eprintln!("   targeting the correct cluster manually.");
            eprintln!();
            eprintln!("   Proceeding with secret creation...\n");
        }
    }

    // Seal the secret
    eprintln!("Sealing secret...");
    let sealed_secret = k8s::seal_secret(&request)?;
    eprintln!("✓ SealedSecret created successfully!\n");

    // Output the sealed secret
    let output = match cmd.output.as_str() {
        "json" => serde_json::to_string_pretty(&sealed_secret)?,
        _ => sealed_secret.to_yaml()?,
    };

    // Print to stdout (for piping to file or kubectl)
    println!("{}", output);

    Ok(())
}

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

    #[test]
    fn test_create_command_interactive_not_implemented() {
        let cmd = CreateCommand {
            name: "test-secret".to_string(),
            namespace: "default".to_string(),
            from_literal: vec!["key=value".to_string()],
            labels: vec![],
            annotations: vec![],
            output: "yaml".to_string(),
            interactive: true,
        };

        let result = create_command(&cmd);
        assert!(result.is_err());
        // Interactive mode should fail since it's not implemented yet
    }

    #[test]
    fn test_create_command_validation_fails() {
        let cmd = CreateCommand {
            name: "".to_string(), // Invalid empty name
            namespace: "default".to_string(),
            from_literal: vec!["key=value".to_string()],
            labels: vec![],
            annotations: vec![],
            output: "yaml".to_string(),
            interactive: false,
        };

        let result = create_command(&cmd);
        assert!(result.is_err());
    }

    #[test]
    fn test_create_command_no_literals() {
        let cmd = CreateCommand {
            name: "test-secret".to_string(),
            namespace: "default".to_string(),
            from_literal: vec![], // No key-value pairs
            labels: vec![],
            annotations: vec![],
            output: "yaml".to_string(),
            interactive: false,
        };

        let result = create_command(&cmd);
        assert!(result.is_err());
        // Validation catches this error - exact message depends on when it's caught
    }
}