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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,18 @@ helm plugin install https://github.com/zipstack/helm-values-manager
## Quick Start

1. Initialize a new configuration:

```bash
helm values-manager init
```

This creates:

- `values-manager.yaml` configuration file
- `values` directory with environment files (`dev.yaml`, `staging.yaml`, `prod.yaml`)

2. View available commands:

```bash
helm values-manager --help
```
Expand All @@ -52,35 +55,41 @@ helm values-manager --help
### Setup Development Environment

1. Clone the repository:

```bash
git clone https://github.com/zipstack/helm-values-manager
cd helm-values-manager
```

2. Create and activate a virtual environment:

```bash
python -m venv venv
source venv/bin/activate # On Windows: .\venv\Scripts\activate
```

3. Install development dependencies:

```bash
pip install -e ".[dev]"
```

4. Install pre-commit hooks:

```bash
pre-commit install
```

### Running Tests

Run tests with tox (will test against multiple Python versions):

```bash
tox
```

Run tests for a specific Python version:

```bash
tox -e py39 # For Python 3.9
```
Expand All @@ -95,6 +104,7 @@ This project uses several tools to maintain code quality:
- **flake8**: Style guide enforcement

Run all code quality checks manually:

```bash
pre-commit run --all-files
```
Expand Down
82 changes: 82 additions & 0 deletions docs/Design/low-level-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,88 @@ Implementations:
- Azure Key Vault Backend
- Additional backends can be easily added

### 5. Schema Validation

The configuration system uses JSON Schema validation to ensure data integrity and consistency:

```mermaid
classDiagram
class SchemaValidator {
+validate_config(data: dict) None
-load_schema() dict
-handle_validation_error(error: ValidationError) str
}

class HelmValuesConfig {
+from_dict(data: dict) HelmValuesConfig
+to_dict() dict
+validate() None
}

HelmValuesConfig ..> SchemaValidator : uses
```

#### Schema Structure

The schema (`schemas/v1.json`) defines:
1. **Version Control**
- Schema version validation
- Backward compatibility checks

2. **Deployment Configuration**
- Backend type validation (git-secret, aws, azure, gcp)
- Authentication method validation
- Backend-specific configuration validation

3. **Value Configuration**
- Path format validation (dot notation)
- Required/optional field validation
- Sensitive value handling
- Environment-specific value validation

#### Validation Points

Schema validation occurs at critical points:
1. **Configuration Loading** (`from_dict`)
- Validates complete configuration structure
- Ensures all required fields are present
- Validates data types and formats

2. **Pre-save Validation** (`to_dict`)
- Ensures configuration remains valid after modifications
- Validates new values match schema requirements

3. **Path Addition** (`add_config_path`)
- Validates new path format
- Ensures path uniqueness
- Validates metadata structure

#### Error Handling

The validation system provides:
1. **Detailed Error Messages**
- Exact location of validation failures
- Clear explanation of validation rules
- Suggestions for fixing issues

2. **Validation Categories**
- Schema version mismatches
- Missing required fields
- Invalid value formats
- Backend configuration errors
- Authentication configuration errors

3. **Error Recovery**
- Validation before persistence
- Prevents invalid configurations from being saved
- Maintains system consistency

This validation ensures:
- Configuration integrity
- Consistent data structure
- Clear error reporting
- Safe configuration updates

## Implementation Details

### 1. Configuration Structure
Expand Down
30 changes: 14 additions & 16 deletions docs/Development/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
- [x] Add tests for ConfigMetadata
- [x] Integrate with PathData

### Schema Validation Integration
- [x] Add Basic Schema Validation
- [x] Create test_schema_validation.py
- [x] Test valid configuration loading
- [x] Test invalid configuration detection
- [x] Test error message clarity
- [x] Add schema validation to HelmValuesConfig
- [x] Add jsonschema dependency
- [x] Implement validation in from_dict
- [x] Add clear error messages
- [x] Update documentation
- [x] Schema documentation in low-level design
- [x] Example configuration in design docs

### ConfigMetadata
- [x] Implement ConfigMetadata class
- [x] Add metadata attributes
Expand All @@ -45,21 +59,6 @@
- [x] Implement from_dict() static method
- [x] Add tests for serialization/deserialization

### HelmValuesConfig Refactoring
- [ ] Remove PlainTextBackend references
- [ ] Update imports and dependencies
- [ ] Remove plaintext.py
- [ ] Update tests
- [ ] Implement unified path storage
- [ ] Add _path_map dictionary
- [ ] Migrate existing code to use _path_map
- [ ] Update tests for new structure
- [ ] Update value management
- [ ] Refactor set_value() to use Value class
- [ ] Refactor get_value() to use Value class
- [ ] Add value validation in set operations
- [ ] Update tests for new value handling

### Backend System
- [ ] Clean up base ValueBackend
- [ ] Update interface methods
Expand Down Expand Up @@ -97,7 +96,6 @@
- [x] Value class tests
- [x] PathData class tests
- [x] ConfigMetadata tests
- [ ] HelmValuesConfig tests
- [ ] Backend tests
- [ ] Command tests
- [ ] Add integration tests
Expand Down
31 changes: 17 additions & 14 deletions helm_values_manager/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from abc import ABC, abstractmethod
from typing import Dict
from typing import Dict, Union


class ValueBackend(ABC):
Expand Down Expand Up @@ -60,34 +60,37 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
raise ValueError(f"Invalid auth type: {auth_config['type']}. " f"Must be one of: {', '.join(valid_types)}")

@abstractmethod
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
"""
Get a value from storage.
Get a value from the backend.

Args:
path: The configuration path
environment: The environment name
resolve: If True, resolve any secret references to their actual values.
If False, return the raw value which may be a secret reference.
path: The configuration path (e.g., "app.replicas")
environment: The environment name (e.g., "dev", "prod")
resolve: Whether to resolve any secret references

Returns:
str: The value (resolved or raw depending on resolve parameter)
The value from the backend, can be a string, number, boolean, or None

Raises:
ValueError: If the value doesn't exist
RuntimeError: If backend operation fails
"""
pass

@abstractmethod
def set_value(self, path: str, environment: str, value: str) -> None:
"""Set a value in the storage backend.
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
"""
Set a value in the backend.

Args:
path: The configuration path (e.g., "app.replicas")
environment: The environment name (e.g., "dev", "prod")
value: The value to store. Must be a string.
value: The value to store, can be a string, number, boolean, or None

Raises:
ValueError: If the value is invalid
ConnectionError: If there's an error connecting to the backend
PermissionError: If there's an authentication or authorization error
ValueError: If the value is not a string, number, boolean, or None
RuntimeError: If backend operation fails
"""
pass

Expand Down
22 changes: 11 additions & 11 deletions helm_values_manager/backends/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
This module provides a simple in-memory backend for storing non-sensitive values.
"""

from typing import Dict
from typing import Dict, Union

from helm_values_manager.backends.base import ValueBackend

Expand All @@ -19,24 +19,23 @@ class SimpleValueBackend(ValueBackend):
def __init__(self) -> None:
"""Initialize an empty in-memory storage."""
super().__init__({"type": "direct"}) # Simple backend doesn't need auth
self._storage: Dict[str, str] = {}
self._storage: Dict[str, Union[str, int, float, bool, None]] = {}

def _get_storage_key(self, path: str, environment: str) -> str:
"""Generate a unique storage key."""
return f"{path}:{environment}"

def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
"""
Get a value from the in-memory storage.

Args:
path: The configuration path (e.g., "app.replicas")
environment: The environment name (e.g., "dev", "prod")
resolve: If True, resolve any secret references to their actual values.
If False, return the raw value which may be a secret reference.
resolve: Whether to resolve any secret references

Returns:
str: The value (resolved or raw depending on resolve parameter)
The value from the backend, can be a string, number, boolean, or None

Raises:
ValueError: If the value doesn't exist
Expand All @@ -46,20 +45,21 @@ def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
raise ValueError(f"No value found for {path} in {environment}")
return self._storage[key]

def set_value(self, path: str, environment: str, value: str) -> None:
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
"""
Set a value in the in-memory storage.

Args:
path: The configuration path (e.g., "app.replicas")
environment: The environment name (e.g., "dev", "prod")
value: The value to store
value: The value to store, can be a string, number, boolean, or None

Raises:
ValueError: If the value is not a string
ValueError: If the value is not a string, number, boolean, or None
"""
if not isinstance(value, str):
raise ValueError("Value must be a string")
if not isinstance(value, (str, int, float, bool, type(None))):
raise ValueError("Value must be a string, number, boolean, or None")

key = self._get_storage_key(path, environment)
self._storage[key] = value

Expand Down
Loading