Skip to content

Commit 39edb56

Browse files
authored
Merge pull request #29 from Zipstack/feature/add-generate-command
[feat] Add generate command and prepare for 0.1.0 release
2 parents 896a604 + 86758a1 commit 39edb56

File tree

13 files changed

+977
-40
lines changed

13 files changed

+977
-40
lines changed

.github/dependabot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
version: 2
77
updates:
8-
- package-ecosystem: "" # See documentation for possible values
8+
- package-ecosystem: "pip" # Python package management
99
directory: "/" # Location of package manifests
1010
schedule:
1111
interval: "weekly"

README.md

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111

1212
🚀 A powerful Helm plugin for managing values and secrets across multiple environments.
1313

14+
## The Problem
15+
16+
Managing Helm values across multiple environments (dev, staging, prod) is challenging:
17+
18+
- 🔀 **Values Sprawl**: Values spread across multiple files become hard to track
19+
- 🔍 **Configuration Discovery**: Difficult to know what values can be configured
20+
-**Missing Values**: No validation for required values before deployment
21+
- 🔐 **Secret Management**: Sensitive data mixed with regular values
22+
- 📝 **Documentation**: Values often lack descriptions and context
23+
24+
Helm Values Manager solves these challenges by providing a structured way to define, validate, and manage values across environments, with built-in support for documentation and secret handling.
25+
1426
## Features
1527

1628
- 🔐 **Secure Secret Management**: Safely handle sensitive data
@@ -33,18 +45,52 @@ helm plugin install https://github.com/zipstack/helm-values-manager
3345

3446
## Quick Start
3547

36-
1. Initialize a new configuration:
48+
1. Initialize a new configuration for your Helm release:
3749

3850
```bash
39-
helm values-manager init
51+
helm values-manager init --release my-app
4052
```
4153

42-
This creates:
54+
2. Add value configurations with descriptions and validation:
55+
56+
```bash
57+
# Add a required configuration
58+
helm values-manager add-value-config --path app.replicas --description "Number of application replicas" --required
4359

44-
- `values-manager.yaml` configuration file
45-
- `values` directory with environment files (`dev.yaml`, `staging.yaml`, `prod.yaml`)
60+
# Add an optional configuration
61+
helm values-manager add-value-config --path app.logLevel --description "Application log level (debug/info/warn/error)"
62+
```
63+
64+
3. Add deployments for different environments:
65+
66+
```bash
67+
helm values-manager add-deployment dev
68+
helm values-manager add-deployment prod
69+
```
70+
71+
4. Set values for each deployment:
72+
73+
```bash
74+
# Set values for dev
75+
helm values-manager set-value --path app.replicas --value 1 --deployment dev
76+
helm values-manager set-value --path app.logLevel --value debug --deployment dev
77+
78+
# Set values for prod
79+
helm values-manager set-value --path app.replicas --value 3 --deployment prod
80+
helm values-manager set-value --path app.logLevel --value info --deployment prod
81+
```
82+
83+
5. Generate values files for deployments:
84+
85+
```bash
86+
# Generate dev values
87+
helm values-manager generate --deployment dev --output ./dev
88+
89+
# Generate prod values
90+
helm values-manager generate --deployment prod --output ./prod
91+
```
4692

47-
2. View available commands:
93+
6. View available commands and options:
4894

4995
```bash
5096
helm values-manager --help

docs/Design/sequence-diagrams.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ sequenceDiagram
1010
participant HelmValuesConfig
1111
participant FileSystem
1212
13-
User->>CLI: helm values init
13+
User->>CLI: helm values-manager init --release test-release
1414
activate CLI
1515
1616
CLI->>BaseCommand: execute()
@@ -52,7 +52,7 @@ sequenceDiagram
5252
participant HelmValuesConfig
5353
participant PathValidator
5454
55-
User->>CLI: helm values add-value-config --path=app.replicas --required
55+
User->>CLI: helm values-manager add-value-config --path app.replicas --description "Number of replicas" --required
5656
activate CLI
5757
5858
CLI->>BaseCommand: execute()
@@ -90,7 +90,7 @@ sequenceDiagram
9090
participant BaseCommand
9191
participant HelmValuesConfig
9292
93-
User->>CLI: helm values add-deployment prod
93+
User->>CLI: helm values-manager add-deployment prod
9494
activate CLI
9595
9696
CLI->>BaseCommand: execute()
@@ -129,7 +129,7 @@ sequenceDiagram
129129
participant HelmValuesConfig
130130
participant ValueBackend
131131
132-
User->>CLI: helm values add-backend aws --deployment=prod --region=us-west-2
132+
User->>CLI: helm values-manager add-backend aws --deployment prod --region us-west-2
133133
activate CLI
134134
135135
CLI->>BaseCommand: execute()
@@ -173,7 +173,7 @@ sequenceDiagram
173173
participant HelmValuesConfig
174174
participant ValueBackend
175175
176-
User->>CLI: helm values add-auth direct --deployment=prod --credentials='{...}'
176+
User->>CLI: helm values-manager add-auth direct --deployment prod --credentials '{...}'
177177
activate CLI
178178
179179
CLI->>BaseCommand: execute()
@@ -219,7 +219,7 @@ sequenceDiagram
219219
participant ValueBackend
220220
participant Storage
221221
222-
User->>CLI: helm values set-value path value --env=prod
222+
User->>CLI: helm values-manager set-value --path app.replicas --value 3 --deployment prod
223223
activate CLI
224224
225225
CLI->>BaseCommand: execute()
@@ -279,7 +279,7 @@ sequenceDiagram
279279
participant ValueBackend
280280
participant Storage
281281
282-
User->>CLI: helm values get-value path --env=prod
282+
User->>CLI: helm values-manager get-value --path app.replicas --deployment prod
283283
activate CLI
284284
285285
CLI->>BaseCommand: execute()
@@ -337,7 +337,7 @@ sequenceDiagram
337337
participant ValueBackend
338338
participant Validator
339339
340-
User->>CLI: helm values validate
340+
User->>CLI: helm values-manager validate
341341
activate CLI
342342
343343
CLI->>BaseCommand: execute()
@@ -388,7 +388,7 @@ sequenceDiagram
388388
participant ValueBackend
389389
participant Generator
390390
391-
User->>CLI: helm values generate --env=prod
391+
User->>CLI: helm values-manager generate --deployment prod --output ./
392392
activate CLI
393393
394394
CLI->>BaseCommand: execute()
@@ -442,7 +442,7 @@ sequenceDiagram
442442
participant Storage
443443
participant TableFormatter
444444
445-
User->>CLI: helm values list-values --env=prod
445+
User->>CLI: helm values-manager list-values --deployment prod
446446
activate CLI
447447
448448
CLI->>BaseCommand: execute()
@@ -490,7 +490,7 @@ sequenceDiagram
490490
participant HelmValuesConfig
491491
participant TableFormatter
492492
493-
User->>CLI: helm values list-deployments
493+
User->>CLI: helm values-manager list-deployments
494494
activate CLI
495495
496496
CLI->>BaseCommand: execute()
@@ -526,7 +526,7 @@ sequenceDiagram
526526
participant ValueBackend
527527
participant Storage
528528
529-
User->>CLI: helm values remove-deployment prod
529+
User->>CLI: helm values-manager remove-deployment prod
530530
activate CLI
531531
532532
CLI->>BaseCommand: execute()
@@ -576,7 +576,7 @@ sequenceDiagram
576576
participant ValueBackend
577577
participant Storage
578578
579-
User->>CLI: helm values remove-value path --env=prod
579+
User->>CLI: helm values-manager remove-value --path app.replicas --deployment prod
580580
activate CLI
581581
582582
CLI->>BaseCommand: execute()
@@ -624,7 +624,7 @@ sequenceDiagram
624624
participant HelmValuesConfig
625625
participant Value
626626
627-
User->>CLI: helm values remove-value-config --path=app.replicas
627+
User->>CLI: helm values-manager remove-value-config --path app.replicas
628628
activate CLI
629629
630630
CLI->>BaseCommand: execute()
@@ -659,6 +659,7 @@ sequenceDiagram
659659
```
660660

661661
Each diagram shows:
662+
662663
- The exact CLI command being executed
663664
- All components involved in processing the command
664665
- The sequence of operations and data flow
@@ -679,3 +680,4 @@ Each diagram shows:
679680
10. `remove-deployment` - Remove a deployment configuration
680681
11. `remove-value` - Remove a value for a specific path and environment
681682
12. `remove-value-config` - Remove a value configuration and its associated values
683+
13. `generate` - Generate a values file for a specific deployment

helm_values_manager/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
"""A Helm plugin to manage values and secrets across environments."""
1+
"""Helm Values Manager package."""
22

3-
__version__ = "0.1.0"
3+
from importlib.metadata import version
4+
5+
__version__ = version("helm-values-manager")
46
__description__ = "A Helm plugin to manage values and secrets across environments."

helm_values_manager/backends/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
5757

5858
valid_types = ["env", "file", "direct", "managed_identity"]
5959
if auth_config["type"] not in valid_types:
60-
raise ValueError(f"Invalid auth type: {auth_config['type']}. " f"Must be one of: {', '.join(valid_types)}")
60+
raise ValueError(f"Invalid auth type: {auth_config['type']}. Must be one of: {', '.join(valid_types)}")
6161

6262
@abstractmethod
6363
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:

helm_values_manager/cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from helm_values_manager.commands.add_deployment_command import AddDeploymentCommand
66
from helm_values_manager.commands.add_value_config_command import AddValueConfigCommand
7+
from helm_values_manager.commands.generate_command import GenerateCommand
78
from helm_values_manager.commands.init_command import InitCommand
89
from helm_values_manager.commands.set_value_command import SetValueCommand
910
from helm_values_manager.models.config_metadata import ConfigMetadata
@@ -125,5 +126,24 @@ def set_value(
125126
raise typer.Exit(code=1) from e
126127

127128

129+
@app.command("generate")
130+
def generate(
131+
deployment: str = typer.Option(
132+
..., "--deployment", "-d", help="Deployment to generate values for (e.g., 'dev', 'prod')"
133+
),
134+
output_path: str = typer.Option(
135+
".", "--output", "-o", help="Directory to output the values file to (default: current directory)"
136+
),
137+
):
138+
"""Generate a values file for a specific deployment."""
139+
try:
140+
command = GenerateCommand()
141+
result = command.execute(deployment=deployment, output_path=output_path)
142+
typer.echo(result)
143+
except Exception as e:
144+
HelmLogger.error("Failed to generate values file: %s", str(e))
145+
raise typer.Exit(code=1) from e
146+
147+
128148
if __name__ == "__main__":
129149
app(prog_name=COMMAND_INFO)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""Command to generate values file for a specific deployment."""
2+
3+
import os
4+
from typing import Any, Dict, Optional
5+
6+
import yaml
7+
8+
from helm_values_manager.commands.base_command import BaseCommand
9+
from helm_values_manager.models.helm_values_config import HelmValuesConfig
10+
from helm_values_manager.utils.logger import HelmLogger
11+
12+
13+
class GenerateCommand(BaseCommand):
14+
"""Command to generate values file for a specific deployment."""
15+
16+
def run(self, config: Optional[HelmValuesConfig] = None, **kwargs) -> str:
17+
"""
18+
Generate a values file for a specific deployment.
19+
20+
Args:
21+
config: The loaded configuration
22+
**kwargs: Command arguments
23+
- deployment (str): The deployment to generate values for (e.g., 'dev', 'prod')
24+
- output_path (str, optional): Directory to output the values file to
25+
26+
Returns:
27+
str: Success message with the path to the generated file
28+
29+
Raises:
30+
ValueError: If deployment is empty
31+
KeyError: If deployment doesn't exist in the configuration
32+
FileNotFoundError: If the configuration file doesn't exist
33+
"""
34+
if config is None:
35+
raise ValueError("Configuration not loaded")
36+
37+
deployment = kwargs.get("deployment")
38+
if not deployment:
39+
raise ValueError("Deployment cannot be empty")
40+
41+
output_path = kwargs.get("output_path", ".")
42+
43+
# Validate that the deployment exists
44+
if deployment not in config.deployments:
45+
raise KeyError(f"Deployment '{deployment}' not found")
46+
47+
# Create output directory if it doesn't exist
48+
if not os.path.exists(output_path):
49+
os.makedirs(output_path)
50+
HelmLogger.debug("Created output directory: %s", output_path)
51+
52+
# Generate values dictionary from configuration
53+
values_dict = self._generate_values_dict(config, deployment)
54+
55+
# Generate filename based on deployment and release
56+
filename = f"{deployment}.{config.release}.values.yaml"
57+
file_path = os.path.join(output_path, filename)
58+
59+
# Write values to file
60+
with open(file_path, "w", encoding="utf-8") as f:
61+
yaml.dump(values_dict, f, default_flow_style=False)
62+
63+
HelmLogger.debug("Generated values file for deployment '%s' at '%s'", deployment, file_path)
64+
return f"Successfully generated values file for deployment '{deployment}' at '{file_path}'"
65+
66+
def _generate_values_dict(self, config: HelmValuesConfig, deployment: str) -> Dict[str, Any]:
67+
"""
68+
Generate a nested dictionary of values from the configuration.
69+
70+
Args:
71+
config: The loaded configuration
72+
deployment: The deployment to generate values for
73+
74+
Returns:
75+
Dict[str, Any]: Nested dictionary of values
76+
77+
Raises:
78+
ValueError: If a required value is missing for the deployment
79+
"""
80+
values_dict = {}
81+
missing_required_paths = []
82+
83+
# Get all paths from the configuration
84+
for path in config._path_map.keys():
85+
path_data = config._path_map[path]
86+
87+
# Check if this is a required value
88+
is_required = path_data._metadata.required
89+
90+
# Get the value for this path and deployment
91+
value = config.get_value(path, deployment, resolve=True)
92+
93+
# If the value is None and it's required, add to missing list
94+
if value is None and is_required:
95+
missing_required_paths.append(path)
96+
continue
97+
98+
# Skip if no value is set
99+
if value is None:
100+
continue
101+
102+
# Convert dot-separated path to nested dictionary
103+
path_parts = path.split(".")
104+
current_dict = values_dict
105+
106+
# Navigate to the correct nested level
107+
for i, part in enumerate(path_parts):
108+
# If we're at the last part, set the value
109+
if i == len(path_parts) - 1:
110+
current_dict[part] = value
111+
else:
112+
# Create nested dictionary if it doesn't exist
113+
if part not in current_dict:
114+
current_dict[part] = {}
115+
current_dict = current_dict[part]
116+
117+
# If there are missing required values, raise an error
118+
if missing_required_paths:
119+
paths_str = ", ".join(missing_required_paths)
120+
raise ValueError(f"Missing required values for deployment '{deployment}': {paths_str}")
121+
122+
return values_dict

0 commit comments

Comments
 (0)