1use crate::models::{SecretKeyValue, SecretRequest};
7use crate::ValidationError;
8use dialoguer::{Confirm, Input, Password};
9use std::collections::HashMap;
10
11pub fn interactive_create() -> Result<SecretRequest, anyhow::Error> {
28 eprintln!("\n=== Interactive Sealed Secret Creation ===\n");
29
30 let name: String = Input::new()
32 .with_prompt("Secret name (lowercase alphanumeric, hyphens, dots)")
33 .validate_with(|input: &String| -> Result<(), &str> {
34 crate::models::validate_secret_name(input).map_err(|_| {
35 "Invalid secret name. Must follow Kubernetes naming rules:\n \
36 - lowercase alphanumeric, hyphens, or dots\n \
37 - must start and end with alphanumeric\n \
38 - max 253 characters"
39 })
40 })
41 .interact_text()?;
42
43 let namespace: String = Input::new()
45 .with_prompt("Namespace (lowercase alphanumeric with hyphens)")
46 .validate_with(|input: &String| -> Result<(), &str> {
47 crate::models::validate_namespace(input).map_err(|_| {
48 "Invalid namespace. Must follow Kubernetes naming rules:\n \
49 - lowercase alphanumeric with hyphens\n \
50 - must start and end with alphanumeric\n \
51 - max 63 characters"
52 })
53 })
54 .interact_text()?;
55
56 eprintln!("\n--- Secret Data ---");
58 eprintln!("Enter key-value pairs (leave key empty to finish)\n");
59
60 let mut data = Vec::new();
61 loop {
62 let key: String = Input::new()
63 .with_prompt("Secret key")
64 .allow_empty(true)
65 .interact_text()?;
66
67 if key.is_empty() {
68 if data.is_empty() {
69 eprintln!("⚠️ At least one key-value pair is required");
70 continue;
71 }
72 break;
73 }
74
75 if let Err(e) = crate::models::validate_key_name(&key) {
77 eprintln!("⚠️ Invalid key: {}", e);
78 eprintln!(" Keys must start with letter or underscore, followed by alphanumeric or underscores\n");
79 continue;
80 }
81
82 if data.iter().any(|kv: &SecretKeyValue| kv.key == key) {
84 eprintln!("⚠️ Key '{}' already exists\n", key);
85 continue;
86 }
87
88 let value: String = Password::new()
90 .with_prompt(format!("Secret value for '{}'", key))
91 .interact()?;
92
93 if value.is_empty() {
94 eprintln!("⚠️ Value cannot be empty\n");
95 continue;
96 }
97
98 data.push(SecretKeyValue::new(key, value)?);
99 eprintln!("✓ Added\n");
100 }
101
102 eprintln!("\n--- Labels (Optional) ---");
104 let add_labels = Confirm::new()
105 .with_prompt("Add labels to the secret?")
106 .default(false)
107 .interact()?;
108
109 let mut labels = HashMap::new();
110 if add_labels {
111 eprintln!("Enter labels (leave key empty to finish)\n");
112 loop {
113 let key: String = Input::new()
114 .with_prompt("Label key")
115 .allow_empty(true)
116 .interact_text()?;
117
118 if key.is_empty() {
119 break;
120 }
121
122 let value: String = Input::new()
123 .with_prompt(format!("Label value for '{}'", key))
124 .interact_text()?;
125
126 labels.insert(key, value);
127 eprintln!("✓ Added\n");
128 }
129 }
130
131 eprintln!("\n--- Annotations (Optional) ---");
133 let add_annotations = Confirm::new()
134 .with_prompt("Add annotations to the secret?")
135 .default(false)
136 .interact()?;
137
138 let mut annotations = HashMap::new();
139 if add_annotations {
140 eprintln!("Enter annotations (leave key empty to finish)\n");
141 loop {
142 let key: String = Input::new()
143 .with_prompt("Annotation key")
144 .allow_empty(true)
145 .interact_text()?;
146
147 if key.is_empty() {
148 break;
149 }
150
151 let value: String = Input::new()
152 .with_prompt(format!("Annotation value for '{}'", key))
153 .interact_text()?;
154
155 annotations.insert(key, value);
156 eprintln!("✓ Added\n");
157 }
158 }
159
160 eprintln!("\n=== Summary ===");
162 eprintln!("Name: {}", name);
163 eprintln!("Namespace: {}", namespace);
164 eprintln!(
165 "Keys: {}",
166 data.iter()
167 .map(|kv| kv.key.as_str())
168 .collect::<Vec<_>>()
169 .join(", ")
170 );
171
172 if !labels.is_empty() {
173 eprintln!(
174 "Labels: {}",
175 labels
176 .iter()
177 .map(|(k, v)| format!("{}={}", k, v))
178 .collect::<Vec<_>>()
179 .join(", ")
180 );
181 } else {
182 eprintln!("Labels: none");
183 }
184
185 if !annotations.is_empty() {
186 eprintln!(
187 "Annotations: {}",
188 annotations
189 .iter()
190 .map(|(k, v)| format!("{}={}", k, v))
191 .collect::<Vec<_>>()
192 .join(", ")
193 );
194 } else {
195 eprintln!("Annotations: none");
196 }
197
198 eprintln!();
199 let confirmed = Confirm::new()
200 .with_prompt("Create SealedSecret with this configuration?")
201 .default(true)
202 .interact()?;
203
204 if !confirmed {
205 return Err(anyhow::anyhow!("Cancelled by user"));
206 }
207
208 SecretRequest::new(name, namespace, labels, annotations, data)
210 .map_err(|e: ValidationError| anyhow::anyhow!("Validation error: {}", e))
211}
212
213#[cfg(test)]
214mod tests {
215 #[test]
220 fn test_interactive_create_function_exists() {
221 }
225}