Skip to content

Commit 15fd14d

Browse files
ritwik-gclaude
andcommitted
Fix values init --force behavior and add --skip-defaults flag
**Problem:** - `values init --force` was creating invalid configurations by skipping required fields without defaults - No option to skip fields with defaults entirely **Solution:** 1. **Fixed --force behavior:** - ✅ Use defaults automatically (no prompt) - ✅ Still prompt for required fields without defaults (ensures valid config) - ✅ Skip optional fields without defaults - ✅ Never create invalid configurations 2. **Added --skip-defaults flag:** - Skip all fields with default values entirely - Can be combined with --force for ultimate speed **New Behaviors:** ```bash # Normal: Full interactive helm values-manager values init --env prod # Fixed --force: Safe + fast helm values-manager values init --env prod --force # → Uses defaults, prompts only for required fields without defaults # New --skip-defaults: Skip defaults entirely helm values-manager values init --env prod --skip-defaults # → Skip defaults, prompt for everything else # Combined: Maximum speed helm values-manager values init --env prod --force --skip-defaults # → Skip defaults, use where needed, prompt only for required without defaults ``` **Testing:** - Added comprehensive tests for all flag combinations - Updated existing tests to expect new safe behavior - All 121 tests passing with 81% coverage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 741866a commit 15fd14d

File tree

4 files changed

+269
-33
lines changed

4 files changed

+269
-33
lines changed

guide/idea phase2.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
- Support loading `.env` file for ENV based secrets
2+
- May be an option 2 specify schema path?
23
- Support to add schemas without interaction.
3-
- value from multiple options
4-
-
4+
- Updating schema or schema migration. Cases like default value change / path change / deletion etc.
5+
- value from multiple options (eg: value should be one of [a,b,c])
6+
- Nice to have: completion based on resources in json files.
7+
8+
bugs
9+
- While using `helm-values-manager values init` or `helm-values-manager values set` what is the difference between n/skip both seems to do same functionality
10+
11+
- `helm-values-manager values init -f` is only supposed to skip default value's prompt and ask others. But looks like it add the default values in to the values json file while not prompting others. Either -f should skip the defaults all together and ask about others. Or we should add new option to skip defaults. What do you think?

helm_values_manager/commands/values.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ def init_command(
275275
env: str = typer.Option(..., "--env", "-e", help="Environment name"),
276276
schema_path: str = typer.Option("schema.json", "--schema", "-s", help="Path to schema file"),
277277
values_path: Optional[str] = typer.Option(None, "--values", help="Path to values file"),
278-
force: bool = typer.Option(False, "--force", "-f", help="Use defaults where possible, skip prompts"),
278+
force: bool = typer.Option(False, "--force", "-f", help="Use defaults where possible, prompt only for required fields without defaults"),
279+
skip_defaults: bool = typer.Option(False, "--skip-defaults", help="Skip fields with default values entirely"),
279280
):
280281
"""Interactively set up values for an environment."""
281282
# Load schema
@@ -318,31 +319,39 @@ def init_command(
318319
if schema_value.default is not None:
319320
console.print(f" Default: {schema_value.default}")
320321

321-
# Prompt for action
322-
if force:
323-
# In force mode, use defaults where possible
324-
if schema_value.default is not None:
325-
values[schema_value.key] = schema_value.default
326-
set_count += 1
327-
console.print(f" → Using default value: {schema_value.default}")
328-
continue
329-
elif schema_value.required:
330-
# Can't skip required values without defaults in force mode
331-
console.print(f" → Required field with no default, skipping")
332-
skipped_count += 1
333-
skipped_required.append(schema_value.key)
334-
continue
335-
else:
336-
# Skip optional fields without defaults
337-
skipped_count += 1
338-
continue
322+
# Determine behavior based on flags
323+
has_default = schema_value.default is not None
324+
325+
# Handle skip-defaults flag
326+
if skip_defaults and has_default:
327+
skipped_count += 1
328+
console.print(f" → Skipping field with default value")
329+
continue
339330

340-
# Interactive mode
341-
action = Prompt.ask(
342-
f"\nSet value for '{schema_value.key}'?",
343-
choices=["y", "n", "skip"],
344-
default="y"
345-
)
331+
# Handle force mode
332+
if force and has_default:
333+
# Use defaults automatically in force mode
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 force and not has_default and not schema_value.required:
339+
# Skip optional fields without defaults in force mode
340+
skipped_count += 1
341+
console.print(f" → Skipping optional field without default")
342+
continue
343+
elif force and not has_default and schema_value.required:
344+
# Required fields without defaults must be prompted even in force mode
345+
console.print(f" → Required field with no default, prompting...")
346+
# Skip the "Set value?" prompt and go directly to value input
347+
action = "y" # Force yes for required fields in force mode
348+
else:
349+
# Interactive mode - ask if user wants to set value
350+
action = Prompt.ask(
351+
f"\nSet value for '{schema_value.key}'?",
352+
choices=["y", "n", "skip"],
353+
default="y"
354+
)
346355

347356
if action == "skip":
348357
skip_all = True

tests/test_init_flags.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"""Tests for values init command flag behavior."""
2+
import json
3+
from pathlib import Path
4+
5+
import pytest
6+
from typer.testing import CliRunner
7+
8+
from helm_values_manager.cli import app
9+
10+
runner = CliRunner()
11+
12+
13+
def test_force_mode_fixed_behavior(tmp_path):
14+
"""Test that --force mode prompts for required fields without defaults."""
15+
with runner.isolated_filesystem(temp_dir=tmp_path):
16+
# Create schema with mix of fields
17+
schema_data = {
18+
"version": "1.0",
19+
"values": [
20+
{
21+
"key": "app-name",
22+
"path": "app.name",
23+
"description": "Application name",
24+
"type": "string",
25+
"required": True,
26+
"default": "myapp"
27+
},
28+
{
29+
"key": "port",
30+
"path": "app.port",
31+
"description": "Application port",
32+
"type": "number",
33+
"required": True
34+
# No default - should be prompted even in force mode
35+
},
36+
{
37+
"key": "debug",
38+
"path": "app.debug",
39+
"description": "Debug mode",
40+
"type": "boolean",
41+
"required": False,
42+
"default": False
43+
},
44+
{
45+
"key": "features",
46+
"path": "app.features",
47+
"description": "Feature flags",
48+
"type": "array",
49+
"required": False
50+
# No default - should be skipped in force mode
51+
}
52+
]
53+
}
54+
55+
with open("schema.json", "w") as f:
56+
json.dump(schema_data, f, indent=2)
57+
58+
# Test force mode with input for required field
59+
result = runner.invoke(app, [
60+
"values", "init",
61+
"--env", "test",
62+
"--force"
63+
], input="8080\n") # Input for port
64+
65+
assert result.exit_code == 0
66+
67+
# Check output messages
68+
assert "Using default value: myapp" in result.output
69+
assert "Using default value: False" in result.output
70+
assert "Required field with no default, prompting" in result.output
71+
assert "Skipping optional field without default" in result.output
72+
73+
# Check generated values file
74+
with open("values-test.json") as f:
75+
values = json.load(f)
76+
77+
expected_values = {
78+
"app-name": "myapp", # From default
79+
"port": 8080, # From user input
80+
"debug": False # From default
81+
# features should not be present (skipped)
82+
}
83+
84+
assert values == expected_values
85+
86+
87+
def test_skip_defaults_mode(tmp_path):
88+
"""Test that --skip-defaults skips fields with defaults."""
89+
with runner.isolated_filesystem(temp_dir=tmp_path):
90+
# Create schema
91+
schema_data = {
92+
"version": "1.0",
93+
"values": [
94+
{
95+
"key": "app-name",
96+
"path": "app.name",
97+
"description": "Application name",
98+
"type": "string",
99+
"required": True,
100+
"default": "myapp"
101+
},
102+
{
103+
"key": "port",
104+
"path": "app.port",
105+
"description": "Application port",
106+
"type": "number",
107+
"required": True
108+
},
109+
{
110+
"key": "debug",
111+
"path": "app.debug",
112+
"description": "Debug mode",
113+
"type": "boolean",
114+
"required": False,
115+
"default": False
116+
}
117+
]
118+
}
119+
120+
with open("schema.json", "w") as f:
121+
json.dump(schema_data, f, indent=2)
122+
123+
# Test skip-defaults mode
124+
result = runner.invoke(app, [
125+
"values", "init",
126+
"--env", "test",
127+
"--skip-defaults"
128+
], input="y\n8080\n") # y for port, 8080 as value
129+
130+
assert result.exit_code == 0
131+
132+
# Check output messages
133+
assert "Skipping field with default value" in result.output
134+
135+
# Check generated values file
136+
with open("values-test.json") as f:
137+
values = json.load(f)
138+
139+
# Should only have port (no defaults used)
140+
expected_values = {
141+
"port": 8080
142+
}
143+
144+
assert values == expected_values
145+
146+
147+
def test_force_and_skip_defaults_combined(tmp_path):
148+
"""Test --force and --skip-defaults together."""
149+
with runner.isolated_filesystem(temp_dir=tmp_path):
150+
# Create schema
151+
schema_data = {
152+
"version": "1.0",
153+
"values": [
154+
{
155+
"key": "app-name",
156+
"path": "app.name",
157+
"description": "Application name",
158+
"type": "string",
159+
"required": False,
160+
"default": "myapp"
161+
},
162+
{
163+
"key": "port",
164+
"path": "app.port",
165+
"description": "Application port",
166+
"type": "number",
167+
"required": True
168+
},
169+
{
170+
"key": "optional-port",
171+
"path": "app.optionalPort",
172+
"description": "Optional port",
173+
"type": "number",
174+
"required": False
175+
}
176+
]
177+
}
178+
179+
with open("schema.json", "w") as f:
180+
json.dump(schema_data, f, indent=2)
181+
182+
# Test both flags together
183+
result = runner.invoke(app, [
184+
"values", "init",
185+
"--env", "test",
186+
"--force",
187+
"--skip-defaults"
188+
], input="8080\n") # Input for required port
189+
190+
assert result.exit_code == 0
191+
192+
# Check output messages
193+
assert "Skipping field with default value" in result.output # app-name skipped
194+
assert "Required field with no default, prompting" in result.output # port prompted
195+
assert "Skipping optional field without default" in result.output # optional-port skipped
196+
197+
# Check generated values file
198+
with open("values-test.json") as f:
199+
values = json.load(f)
200+
201+
# Should only have port
202+
expected_values = {
203+
"port": 8080
204+
}
205+
206+
assert values == expected_values
207+
208+
209+
def test_force_mode_help_text():
210+
"""Test that help text is updated correctly."""
211+
result = runner.invoke(app, ["values", "init", "--help"])
212+
assert result.exit_code == 0
213+
# Check that the help text contains key phrases (may be wrapped across lines)
214+
assert "required fields without defaults" in result.output
215+
assert "--skip-defaults" in result.output
216+
assert "Skip fields with default values entirely" in result.output

tests/test_values_init.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,31 @@ def create_test_schema_for_init():
6060

6161

6262
def test_values_init_force_mode_with_defaults(tmp_path):
63-
"""Test init command in force mode uses defaults where available."""
63+
"""Test init command in force mode uses defaults and prompts for required fields without defaults."""
6464
with runner.isolated_filesystem(temp_dir=tmp_path):
6565
schema = create_test_schema_for_init()
6666
with open("schema.json", "w") as f:
6767
json.dump(schema.model_dump(), f)
6868

69-
result = runner.invoke(app, ["values", "init", "--env", "dev", "--force"])
69+
# Force mode now prompts for required fields without defaults
70+
# Order: api-key first (1 for env, SECRET_KEY for name), then port (8080)
71+
result = runner.invoke(app, ["values", "init", "--env", "dev", "--force"],
72+
input="1\nSECRET_KEY\n8080\n")
7073

7174
assert result.exit_code == 0
7275
assert "Using default value: myapp" in result.output
7376
assert "Using default value: False" in result.output
74-
assert "Required field with no default, skipping" in result.output # port and api-key
77+
assert "Required field with no default, prompting" in result.output # port and api-key
7578

7679
# Check saved values (flat structure)
7780
with open("values-dev.json") as f:
7881
values = json.load(f)
7982

8083
assert values["app-name"] == "myapp"
8184
assert values["debug"] is False
82-
assert "port" not in values # Required but no default
83-
assert "api-key" not in values # Required but sensitive
85+
assert values["port"] == 8080 # Now included because we prompted for it
86+
assert values["api-key"]["type"] == "env" # Secret now set up
87+
assert values["api-key"]["name"] == "SECRET_KEY"
8488

8589

8690
def test_values_init_interactive_set_values(tmp_path):
@@ -258,7 +262,7 @@ def test_values_init_custom_schema_and_values_path(tmp_path):
258262
"--schema", "config/custom-schema.json",
259263
"--values", "config/custom-values-custom.json",
260264
"--force"
261-
])
265+
], input="1\nCUSTOM_SECRET\n8080\n") # Input order: api-key secret, then port
262266

263267
assert result.exit_code == 0
264268

0 commit comments

Comments
 (0)