Skip to content

Commit 9f1edc3

Browse files
ritwik-gclaude
andcommitted
Implement Task 3.7: Add interactive values init command and update validation for single environment
### Task 3.7: Interactive Environment Setup - Added 'values init --env <env>' command for interactive value setup - Supports required-first ordering with optional fields after - Interactive prompts for each unset value with type validation - Extensible secret configuration with environment variable support - Skip individual fields or skip all remaining fields - Force mode to use defaults where possible - Comprehensive error handling and user feedback - Shows summary of set/skipped values and warnings for unset required fields ### Validation Updates - Made --env parameter required for validate command (single environment focus) - Updated validator to work with flat file structure (no environment key nesting) - Removed multi-environment validation (one environment per command execution) - Updated all tests to match single-environment approach - Fixed environment name display in validation errors ### Architecture Consistency - All commands now work with single environment per execution - Values files use flat structure (no environment key nesting) - Consistent --env parameter across all commands - Simplified file format reduces confusion 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5f5c127 commit 9f1edc3

File tree

6 files changed

+527
-104
lines changed

6 files changed

+527
-104
lines changed

guide/prompt_plan.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,36 @@ Update both `values set` and `values set-secret` commands to:
147147
c. Abort if not confirmed
148148
3. For `values set-secret`:
149149
a. Show both current type and name if available
150-
b. Example prompt:
150+
b. Example prompt:
151151
"Key 'database-password' already set as env:PROD_DB_PASSWORD
152152
Overwrite? [y/N]"
153153
4. Add --force flag to bypass confirmation
154154
```
155155
**Status**: Completed - Confirmation prompts with --force flag bypass
156156

157+
#### Task 3.7: Interactive Environment Setup ✅
158+
```prompt
159+
Implement `values init --env <env>` command that:
160+
1. Loads schema and checks for unset values in specified environment
161+
2. For each unset value (required first, then optional):
162+
a. Show key, description, type, and required status
163+
b. Prompt: "Set value for [key]? (Y/n/skip)"
164+
- Y: Proceed to value input (normal/sensitive based on schema)
165+
- n: Skip this field
166+
- skip: Skip all remaining
167+
3. For sensitive values:
168+
a. Use `set-secret` workflow (with type selection)
169+
4. For non-sensitive values:
170+
a. Show current default (if exists)
171+
b. Prompt for value with type validation
172+
5. After all fields:
173+
a. Show summary:
174+
"Set X values, skipped Y required values"
175+
b. List any unset required values
176+
6. Add --force to disable prompts (use defaults where possible)
177+
```
178+
**Status**: Completed - Interactive setup with type validation and extensible secret support
179+
157180
### Phase 4: Core Engine
158181
#### Task 4.1: Validator ✅
159182
```prompt

helm_values_manager/commands/validate.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212
def validate_command(
13+
env: str = typer.Option(..., "--env", "-e", help="Environment to validate"),
1314
schema: str = typer.Option(
1415
"schema.json",
1516
"--schema",
@@ -19,24 +20,17 @@ def validate_command(
1920
values: Optional[str] = typer.Option(
2021
None,
2122
"--values",
22-
help="Base path for values files (directory containing values-{env}.json files)",
23-
),
24-
env: Optional[str] = typer.Option(
25-
None,
26-
"--env",
27-
"-e",
28-
help="Validate specific environment only",
23+
help="Path to values file (default: values-{env}.json)",
2924
),
3025
):
31-
"""Validate schema and values files."""
26+
"""Validate schema and values file for a specific environment."""
3227
# Import here to avoid circular imports
33-
from helm_values_manager.validator import validate_command as run_validation
28+
from helm_values_manager.validator import validate_single_environment
3429

3530
schema_path = Path(schema)
36-
values_base_path = Path(values) if values else Path(".")
3731

38-
# Run validation
39-
success = run_validation(schema_path, values_base_path, env)
32+
# Run validation for single environment
33+
success = validate_single_environment(schema_path, env, values)
4034

4135
if not success:
4236
raise typer.Exit(code=1)

helm_values_manager/commands/values.py

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,158 @@ def remove_command(
275275
# Save values
276276
save_values(values, env, values_path)
277277

278-
console.print(f"[green]✓[/green] Removed '{key}' from environment '{env}'")
278+
console.print(f"[green]✓[/green] Removed '{key}' from environment '{env}'")
279+
280+
281+
@app.command("init")
282+
def init_command(
283+
env: str = typer.Option(..., "--env", "-e", help="Environment name"),
284+
schema_path: str = typer.Option("schema.json", "--schema", "-s", help="Path to schema file"),
285+
values_path: Optional[str] = typer.Option(None, "--values", help="Path to values file"),
286+
force: bool = typer.Option(False, "--force", "-f", help="Use defaults where possible, skip prompts"),
287+
):
288+
"""Interactively set up values for an environment."""
289+
# Load schema
290+
schema = load_schema(schema_path)
291+
if not schema:
292+
console.print("[red]Error:[/red] Schema file not found")
293+
raise typer.Exit(1)
294+
295+
# Load existing values
296+
values = load_values(env, values_path)
297+
298+
# Track statistics
299+
set_count = 0
300+
skipped_count = 0
301+
skipped_required = []
302+
303+
# Sort values: required first, then by key
304+
sorted_values = sorted(schema.values, key=lambda v: (not v.required, v.key))
305+
306+
console.print(f"\n[bold]Setting up values for environment: {env}[/bold]\n")
307+
308+
skip_all = False
309+
310+
for schema_value in sorted_values:
311+
# Skip if already set
312+
if schema_value.key in values:
313+
continue
314+
315+
# Skip if user chose to skip all
316+
if skip_all:
317+
skipped_count += 1
318+
if schema_value.required:
319+
skipped_required.append(schema_value.key)
320+
continue
321+
322+
# Display field info
323+
console.print(f"\n[bold cyan]{schema_value.key}[/bold cyan]")
324+
console.print(f" Description: {schema_value.description}")
325+
console.print(f" Type: {schema_value.type}")
326+
console.print(f" Required: {'Yes' if schema_value.required else 'No'}")
327+
if schema_value.default is not None:
328+
console.print(f" Default: {schema_value.default}")
329+
330+
# Prompt for action
331+
if force:
332+
# In force mode, use defaults where possible
333+
if schema_value.default is not None:
334+
values[schema_value.key] = schema_value.default
335+
set_count += 1
336+
console.print(f" → Using default value: {schema_value.default}")
337+
continue
338+
elif schema_value.required:
339+
# Can't skip required values without defaults in force mode
340+
console.print(f" → [yellow]Warning: Required field with no default, skipping[/yellow]")
341+
skipped_count += 1
342+
skipped_required.append(schema_value.key)
343+
continue
344+
else:
345+
# Skip optional fields without defaults
346+
skipped_count += 1
347+
continue
348+
349+
# Interactive mode
350+
action = Prompt.ask(
351+
f"\nSet value for '{schema_value.key}'?",
352+
choices=["y", "n", "skip"],
353+
default="y"
354+
)
355+
356+
if action == "skip":
357+
skip_all = True
358+
skipped_count += 1
359+
if schema_value.required:
360+
skipped_required.append(schema_value.key)
361+
continue
362+
elif action == "n":
363+
skipped_count += 1
364+
if schema_value.required:
365+
skipped_required.append(schema_value.key)
366+
continue
367+
368+
# Set the value
369+
if schema_value.sensitive:
370+
# Use set-secret workflow
371+
console.print("\n[yellow]This is a sensitive value. Setting up as secret...[/yellow]")
372+
373+
# Display secret type options
374+
console.print(" 1. Environment variable")
375+
console.print(" [dim]2. HashiCorp Vault (coming soon)[/dim]")
376+
console.print(" [dim]3. AWS Secrets Manager (coming soon)[/dim]")
377+
console.print(" [dim]4. Azure Key Vault (coming soon)[/dim]")
378+
379+
# Select secret type
380+
secret_type = Prompt.ask(
381+
"Select secret type",
382+
choices=["1"],
383+
default="1"
384+
)
385+
386+
if secret_type == "1":
387+
env_var_name = Prompt.ask("Environment variable name")
388+
389+
# Check if environment variable exists
390+
if not os.environ.get(env_var_name):
391+
console.print(f"[yellow]⚠ Warning: Environment variable '{env_var_name}' is not set[/yellow]")
392+
393+
values[schema_value.key] = {
394+
"type": "env",
395+
"name": env_var_name
396+
}
397+
set_count += 1
398+
else:
399+
# Regular value
400+
if schema_value.default is not None:
401+
prompt_text = f"Value (default: {schema_value.default})"
402+
default_str = str(schema_value.default)
403+
else:
404+
prompt_text = "Value"
405+
default_str = None
406+
407+
while True:
408+
value_str = Prompt.ask(prompt_text, default=default_str)
409+
410+
try:
411+
parsed_value = parse_value_by_type(value_str, schema_value.type)
412+
values[schema_value.key] = parsed_value
413+
set_count += 1
414+
break
415+
except (ValueError, json.JSONDecodeError, typer.BadParameter) as e:
416+
console.print(f"[red]Error:[/red] {e}")
417+
continue
418+
419+
# Save values
420+
save_values(values, env, values_path)
421+
422+
# Show summary
423+
console.print(f"\n[bold]Summary:[/bold]")
424+
console.print(f" Set {set_count} values")
425+
console.print(f" Skipped {skipped_count} values")
426+
427+
if skipped_required:
428+
console.print(f"\n[yellow]⚠ Warning: The following required values were not set:[/yellow]")
429+
for key in skipped_required:
430+
console.print(f" - {key}")
431+
432+
console.print(f"\n[green]✓[/green] Initialization complete for environment '{env}'")

helm_values_manager/validator.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,24 @@ def _validate_schema(self):
115115
ValidationError("Schema", f"Default value type mismatch for {entry.key}")
116116
)
117117

118-
def _validate_values_for_env(self, env: str):
118+
def _validate_values_for_env(self, env: str, values_file_path: Optional[str] = None):
119119
"""Validate values for a specific environment."""
120-
values_file = self.values_base_path / f"values-{env}.json"
120+
if values_file_path:
121+
values_file = Path(values_file_path)
122+
else:
123+
values_file = self.values_base_path / f"values-{env}.json"
121124

122125
if not values_file.exists():
123126
# Not an error if no values file exists
124127
return
125128

126129
try:
127130
with open(values_file) as f:
128-
values = json.load(f)
131+
env_values = json.load(f)
129132
except json.JSONDecodeError as e:
130133
self.errors.append(ValidationError("Values", f"Invalid JSON in {values_file}: {e}", env))
131134
return
132135

133-
env_values = values.get(env, {})
134-
135136
# Load schema
136137
try:
137138
if not self.schema_path.exists():
@@ -238,6 +239,28 @@ def print_errors(self):
238239
console.print(f" - {str(error)}")
239240

240241

242+
def validate_single_environment(
243+
schema_path: Path,
244+
env: str,
245+
values_file_path: Optional[str] = None
246+
) -> bool:
247+
"""Run validation for a single environment and report results."""
248+
validator = Validator(schema_path, Path("."))
249+
250+
# Validate schema first
251+
validator._validate_schema()
252+
253+
# Then validate values for the specific environment
254+
validator._validate_values_for_env(env, values_file_path)
255+
256+
if len(validator.errors) == 0:
257+
console.print(f"✅ Validation passed for environment: {env}")
258+
return True
259+
else:
260+
validator.print_errors()
261+
return False
262+
263+
241264
def validate_command(
242265
schema_path: Path,
243266
values_base_path: Optional[Path] = None,

0 commit comments

Comments
 (0)