Skip to main content

seal/cli/
commands.rs

1//! CLI command implementations
2
3use crate::cli::args::CreateCommand;
4use crate::k8s;
5use crate::security;
6
7/// Execute the create command
8pub fn create_command(cmd: &CreateCommand) -> Result<(), anyhow::Error> {
9    // Register signal handlers for graceful cleanup
10    security::register_signal_handlers()?;
11
12    // Validate arguments
13    cmd.validate()?;
14
15    // Get SecretRequest from interactive mode or CLI args
16    let request = if cmd.interactive {
17        crate::cli::interactive_create()?
18    } else {
19        cmd.to_secret_request()?
20    };
21
22    // Display cluster context (User Story 2 - Safety feature)
23    match k8s::load_context_sync() {
24        Ok(context) => {
25            eprintln!("\n{}", context.display());
26            eprintln!();
27        }
28        Err(e) => {
29            eprintln!("\n⚠️  Warning: Could not load cluster context");
30            eprintln!("   {}", e);
31            eprintln!();
32            eprintln!("   Possible causes:");
33            eprintln!("   - No kubeconfig file found (~/.kube/config or $KUBECONFIG)");
34            eprintln!("   - Invalid kubeconfig format");
35            eprintln!("   - No current context configured");
36            eprintln!();
37            eprintln!("   You can still create the sealed secret, but verify you're");
38            eprintln!("   targeting the correct cluster manually.");
39            eprintln!();
40            eprintln!("   Proceeding with secret creation...\n");
41        }
42    }
43
44    // Seal the secret
45    eprintln!("Sealing secret...");
46    let sealed_secret = k8s::seal_secret(&request)?;
47    eprintln!("✓ SealedSecret created successfully!\n");
48
49    // Output the sealed secret
50    let output = match cmd.output.as_str() {
51        "json" => serde_json::to_string_pretty(&sealed_secret)?,
52        _ => sealed_secret.to_yaml()?,
53    };
54
55    // Print to stdout (for piping to file or kubectl)
56    println!("{}", output);
57
58    Ok(())
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_create_command_interactive_not_implemented() {
67        let cmd = CreateCommand {
68            name: "test-secret".to_string(),
69            namespace: "default".to_string(),
70            from_literal: vec!["key=value".to_string()],
71            labels: vec![],
72            annotations: vec![],
73            output: "yaml".to_string(),
74            interactive: true,
75        };
76
77        let result = create_command(&cmd);
78        assert!(result.is_err());
79        // Interactive mode should fail since it's not implemented yet
80    }
81
82    #[test]
83    fn test_create_command_validation_fails() {
84        let cmd = CreateCommand {
85            name: "".to_string(), // Invalid empty name
86            namespace: "default".to_string(),
87            from_literal: vec!["key=value".to_string()],
88            labels: vec![],
89            annotations: vec![],
90            output: "yaml".to_string(),
91            interactive: false,
92        };
93
94        let result = create_command(&cmd);
95        assert!(result.is_err());
96    }
97
98    #[test]
99    fn test_create_command_no_literals() {
100        let cmd = CreateCommand {
101            name: "test-secret".to_string(),
102            namespace: "default".to_string(),
103            from_literal: vec![], // No key-value pairs
104            labels: vec![],
105            annotations: vec![],
106            output: "yaml".to_string(),
107            interactive: false,
108        };
109
110        let result = create_command(&cmd);
111        assert!(result.is_err());
112        // Validation catches this error - exact message depends on when it's caught
113    }
114}