Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
- package-ecosystem: "pip" # Python package management
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
58 changes: 52 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

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

## The Problem

Managing Helm values across multiple environments (dev, staging, prod) is challenging:

- 🔀 **Values Sprawl**: Values spread across multiple files become hard to track
- 🔍 **Configuration Discovery**: Difficult to know what values can be configured
- ❌ **Missing Values**: No validation for required values before deployment
- 🔐 **Secret Management**: Sensitive data mixed with regular values
- 📝 **Documentation**: Values often lack descriptions and context

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.

## Features

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

## Quick Start

1. Initialize a new configuration:
1. Initialize a new configuration for your Helm release:

```bash
helm values-manager init
helm values-manager init --release my-app
```

This creates:
2. Add value configurations with descriptions and validation:

```bash
# Add a required configuration
helm values-manager add-value-config --path app.replicas --description "Number of application replicas" --required

- `values-manager.yaml` configuration file
- `values` directory with environment files (`dev.yaml`, `staging.yaml`, `prod.yaml`)
# Add an optional configuration
helm values-manager add-value-config --path app.logLevel --description "Application log level (debug/info/warn/error)"
```

3. Add deployments for different environments:

```bash
helm values-manager add-deployment dev
helm values-manager add-deployment prod
```

4. Set values for each deployment:

```bash
# Set values for dev
helm values-manager set-value --path app.replicas --value 1 --deployment dev
helm values-manager set-value --path app.logLevel --value debug --deployment dev

# Set values for prod
helm values-manager set-value --path app.replicas --value 3 --deployment prod
helm values-manager set-value --path app.logLevel --value info --deployment prod
```

5. Generate values files for deployments:

```bash
# Generate dev values
helm values-manager generate --deployment dev --output ./dev

# Generate prod values
helm values-manager generate --deployment prod --output ./prod
```

2. View available commands:
6. View available commands and options:

```bash
helm values-manager --help
Expand Down
30 changes: 16 additions & 14 deletions docs/Design/sequence-diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ sequenceDiagram
participant HelmValuesConfig
participant FileSystem

User->>CLI: helm values init
User->>CLI: helm values-manager init --release test-release
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -52,7 +52,7 @@ sequenceDiagram
participant HelmValuesConfig
participant PathValidator

User->>CLI: helm values add-value-config --path=app.replicas --required
User->>CLI: helm values-manager add-value-config --path app.replicas --description "Number of replicas" --required
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -90,7 +90,7 @@ sequenceDiagram
participant BaseCommand
participant HelmValuesConfig

User->>CLI: helm values add-deployment prod
User->>CLI: helm values-manager add-deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -129,7 +129,7 @@ sequenceDiagram
participant HelmValuesConfig
participant ValueBackend

User->>CLI: helm values add-backend aws --deployment=prod --region=us-west-2
User->>CLI: helm values-manager add-backend aws --deployment prod --region us-west-2
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -173,7 +173,7 @@ sequenceDiagram
participant HelmValuesConfig
participant ValueBackend

User->>CLI: helm values add-auth direct --deployment=prod --credentials='{...}'
User->>CLI: helm values-manager add-auth direct --deployment prod --credentials '{...}'
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -219,7 +219,7 @@ sequenceDiagram
participant ValueBackend
participant Storage

User->>CLI: helm values set-value path value --env=prod
User->>CLI: helm values-manager set-value --path app.replicas --value 3 --deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -279,7 +279,7 @@ sequenceDiagram
participant ValueBackend
participant Storage

User->>CLI: helm values get-value path --env=prod
User->>CLI: helm values-manager get-value --path app.replicas --deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -337,7 +337,7 @@ sequenceDiagram
participant ValueBackend
participant Validator

User->>CLI: helm values validate
User->>CLI: helm values-manager validate
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -388,7 +388,7 @@ sequenceDiagram
participant ValueBackend
participant Generator

User->>CLI: helm values generate --env=prod
User->>CLI: helm values-manager generate --deployment prod --output ./
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -442,7 +442,7 @@ sequenceDiagram
participant Storage
participant TableFormatter

User->>CLI: helm values list-values --env=prod
User->>CLI: helm values-manager list-values --deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -490,7 +490,7 @@ sequenceDiagram
participant HelmValuesConfig
participant TableFormatter

User->>CLI: helm values list-deployments
User->>CLI: helm values-manager list-deployments
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -526,7 +526,7 @@ sequenceDiagram
participant ValueBackend
participant Storage

User->>CLI: helm values remove-deployment prod
User->>CLI: helm values-manager remove-deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -576,7 +576,7 @@ sequenceDiagram
participant ValueBackend
participant Storage

User->>CLI: helm values remove-value path --env=prod
User->>CLI: helm values-manager remove-value --path app.replicas --deployment prod
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -624,7 +624,7 @@ sequenceDiagram
participant HelmValuesConfig
participant Value

User->>CLI: helm values remove-value-config --path=app.replicas
User->>CLI: helm values-manager remove-value-config --path app.replicas
activate CLI

CLI->>BaseCommand: execute()
Expand Down Expand Up @@ -659,6 +659,7 @@ sequenceDiagram
```

Each diagram shows:

- The exact CLI command being executed
- All components involved in processing the command
- The sequence of operations and data flow
Expand All @@ -679,3 +680,4 @@ Each diagram shows:
10. `remove-deployment` - Remove a deployment configuration
11. `remove-value` - Remove a value for a specific path and environment
12. `remove-value-config` - Remove a value configuration and its associated values
13. `generate` - Generate a values file for a specific deployment
6 changes: 4 additions & 2 deletions helm_values_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""A Helm plugin to manage values and secrets across environments."""
"""Helm Values Manager package."""

__version__ = "0.1.0"
from importlib.metadata import version

__version__ = version("helm-values-manager")
__description__ = "A Helm plugin to manage values and secrets across environments."
2 changes: 1 addition & 1 deletion helm_values_manager/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:

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

@abstractmethod
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
Expand Down
20 changes: 20 additions & 0 deletions helm_values_manager/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from helm_values_manager.commands.add_deployment_command import AddDeploymentCommand
from helm_values_manager.commands.add_value_config_command import AddValueConfigCommand
from helm_values_manager.commands.generate_command import GenerateCommand
from helm_values_manager.commands.init_command import InitCommand
from helm_values_manager.commands.set_value_command import SetValueCommand
from helm_values_manager.models.config_metadata import ConfigMetadata
Expand Down Expand Up @@ -125,5 +126,24 @@ def set_value(
raise typer.Exit(code=1) from e


@app.command("generate")
def generate(
deployment: str = typer.Option(
..., "--deployment", "-d", help="Deployment to generate values for (e.g., 'dev', 'prod')"
),
output_path: str = typer.Option(
".", "--output", "-o", help="Directory to output the values file to (default: current directory)"
),
):
"""Generate a values file for a specific deployment."""
try:
command = GenerateCommand()
result = command.execute(deployment=deployment, output_path=output_path)
typer.echo(result)
except Exception as e:
HelmLogger.error("Failed to generate values file: %s", str(e))
raise typer.Exit(code=1) from e


if __name__ == "__main__":
app(prog_name=COMMAND_INFO)
122 changes: 122 additions & 0 deletions helm_values_manager/commands/generate_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Command to generate values file for a specific deployment."""

import os
from typing import Any, Dict, Optional

import yaml

from helm_values_manager.commands.base_command import BaseCommand
from helm_values_manager.models.helm_values_config import HelmValuesConfig
from helm_values_manager.utils.logger import HelmLogger


class GenerateCommand(BaseCommand):
"""Command to generate values file for a specific deployment."""

def run(self, config: Optional[HelmValuesConfig] = None, **kwargs) -> str:
"""
Generate a values file for a specific deployment.

Args:
config: The loaded configuration
**kwargs: Command arguments
- deployment (str): The deployment to generate values for (e.g., 'dev', 'prod')
- output_path (str, optional): Directory to output the values file to

Returns:
str: Success message with the path to the generated file

Raises:
ValueError: If deployment is empty
KeyError: If deployment doesn't exist in the configuration
FileNotFoundError: If the configuration file doesn't exist
"""
if config is None:
raise ValueError("Configuration not loaded")

deployment = kwargs.get("deployment")
if not deployment:
raise ValueError("Deployment cannot be empty")

output_path = kwargs.get("output_path", ".")

# Validate that the deployment exists
if deployment not in config.deployments:
raise KeyError(f"Deployment '{deployment}' not found")

# Create output directory if it doesn't exist
if not os.path.exists(output_path):
os.makedirs(output_path)
HelmLogger.debug("Created output directory: %s", output_path)

# Generate values dictionary from configuration
values_dict = self._generate_values_dict(config, deployment)

# Generate filename based on deployment and release
filename = f"{deployment}.{config.release}.values.yaml"
file_path = os.path.join(output_path, filename)

# Write values to file
with open(file_path, "w", encoding="utf-8") as f:
yaml.dump(values_dict, f, default_flow_style=False)

HelmLogger.debug("Generated values file for deployment '%s' at '%s'", deployment, file_path)
return f"Successfully generated values file for deployment '{deployment}' at '{file_path}'"

def _generate_values_dict(self, config: HelmValuesConfig, deployment: str) -> Dict[str, Any]:
"""
Generate a nested dictionary of values from the configuration.

Args:
config: The loaded configuration
deployment: The deployment to generate values for

Returns:
Dict[str, Any]: Nested dictionary of values

Raises:
ValueError: If a required value is missing for the deployment
"""
values_dict = {}
missing_required_paths = []

# Get all paths from the configuration
for path in config._path_map.keys():
path_data = config._path_map[path]

# Check if this is a required value
is_required = path_data._metadata.required

# Get the value for this path and deployment
value = config.get_value(path, deployment, resolve=True)

# If the value is None and it's required, add to missing list
if value is None and is_required:
missing_required_paths.append(path)
continue

# Skip if no value is set
if value is None:
continue

# Convert dot-separated path to nested dictionary
path_parts = path.split(".")
current_dict = values_dict

# Navigate to the correct nested level
for i, part in enumerate(path_parts):
# If we're at the last part, set the value
if i == len(path_parts) - 1:
current_dict[part] = value
else:
# Create nested dictionary if it doesn't exist
if part not in current_dict:
current_dict[part] = {}
current_dict = current_dict[part]

# If there are missing required values, raise an error
if missing_required_paths:
paths_str = ", ".join(missing_required_paths)
raise ValueError(f"Missing required values for deployment '{deployment}': {paths_str}")

return values_dict
Loading