Skip to content

Commit 854041f

Browse files
authored
Merge pull request #13 from Zipstack/feature/4-schema-validation
feat: Add schema validation for configuration
2 parents 034a4d4 + f399f74 commit 854041f

File tree

16 files changed

+813
-138
lines changed

16 files changed

+813
-138
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@ helm plugin install https://github.com/zipstack/helm-values-manager
3434
## Quick Start
3535

3636
1. Initialize a new configuration:
37+
3738
```bash
3839
helm values-manager init
3940
```
4041

4142
This creates:
43+
4244
- `values-manager.yaml` configuration file
4345
- `values` directory with environment files (`dev.yaml`, `staging.yaml`, `prod.yaml`)
4446

4547
2. View available commands:
48+
4649
```bash
4750
helm values-manager --help
4851
```
@@ -52,35 +55,41 @@ helm values-manager --help
5255
### Setup Development Environment
5356

5457
1. Clone the repository:
58+
5559
```bash
5660
git clone https://github.com/zipstack/helm-values-manager
5761
cd helm-values-manager
5862
```
5963

6064
2. Create and activate a virtual environment:
65+
6166
```bash
6267
python -m venv venv
6368
source venv/bin/activate # On Windows: .\venv\Scripts\activate
6469
```
6570

6671
3. Install development dependencies:
72+
6773
```bash
6874
pip install -e ".[dev]"
6975
```
7076

7177
4. Install pre-commit hooks:
78+
7279
```bash
7380
pre-commit install
7481
```
7582

7683
### Running Tests
7784

7885
Run tests with tox (will test against multiple Python versions):
86+
7987
```bash
8088
tox
8189
```
8290

8391
Run tests for a specific Python version:
92+
8493
```bash
8594
tox -e py39 # For Python 3.9
8695
```
@@ -95,6 +104,7 @@ This project uses several tools to maintain code quality:
95104
- **flake8**: Style guide enforcement
96105

97106
Run all code quality checks manually:
107+
98108
```bash
99109
pre-commit run --all-files
100110
```

docs/Design/low-level-design.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,88 @@ Implementations:
202202
- Azure Key Vault Backend
203203
- Additional backends can be easily added
204204

205+
### 5. Schema Validation
206+
207+
The configuration system uses JSON Schema validation to ensure data integrity and consistency:
208+
209+
```mermaid
210+
classDiagram
211+
class SchemaValidator {
212+
+validate_config(data: dict) None
213+
-load_schema() dict
214+
-handle_validation_error(error: ValidationError) str
215+
}
216+
217+
class HelmValuesConfig {
218+
+from_dict(data: dict) HelmValuesConfig
219+
+to_dict() dict
220+
+validate() None
221+
}
222+
223+
HelmValuesConfig ..> SchemaValidator : uses
224+
```
225+
226+
#### Schema Structure
227+
228+
The schema (`schemas/v1.json`) defines:
229+
1. **Version Control**
230+
- Schema version validation
231+
- Backward compatibility checks
232+
233+
2. **Deployment Configuration**
234+
- Backend type validation (git-secret, aws, azure, gcp)
235+
- Authentication method validation
236+
- Backend-specific configuration validation
237+
238+
3. **Value Configuration**
239+
- Path format validation (dot notation)
240+
- Required/optional field validation
241+
- Sensitive value handling
242+
- Environment-specific value validation
243+
244+
#### Validation Points
245+
246+
Schema validation occurs at critical points:
247+
1. **Configuration Loading** (`from_dict`)
248+
- Validates complete configuration structure
249+
- Ensures all required fields are present
250+
- Validates data types and formats
251+
252+
2. **Pre-save Validation** (`to_dict`)
253+
- Ensures configuration remains valid after modifications
254+
- Validates new values match schema requirements
255+
256+
3. **Path Addition** (`add_config_path`)
257+
- Validates new path format
258+
- Ensures path uniqueness
259+
- Validates metadata structure
260+
261+
#### Error Handling
262+
263+
The validation system provides:
264+
1. **Detailed Error Messages**
265+
- Exact location of validation failures
266+
- Clear explanation of validation rules
267+
- Suggestions for fixing issues
268+
269+
2. **Validation Categories**
270+
- Schema version mismatches
271+
- Missing required fields
272+
- Invalid value formats
273+
- Backend configuration errors
274+
- Authentication configuration errors
275+
276+
3. **Error Recovery**
277+
- Validation before persistence
278+
- Prevents invalid configurations from being saved
279+
- Maintains system consistency
280+
281+
This validation ensures:
282+
- Configuration integrity
283+
- Consistent data structure
284+
- Clear error reporting
285+
- Safe configuration updates
286+
205287
## Implementation Details
206288

207289
### 1. Configuration Structure

docs/Development/tasks.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@
3535
- [x] Add tests for ConfigMetadata
3636
- [x] Integrate with PathData
3737

38+
### Schema Validation Integration
39+
- [x] Add Basic Schema Validation
40+
- [x] Create test_schema_validation.py
41+
- [x] Test valid configuration loading
42+
- [x] Test invalid configuration detection
43+
- [x] Test error message clarity
44+
- [x] Add schema validation to HelmValuesConfig
45+
- [x] Add jsonschema dependency
46+
- [x] Implement validation in from_dict
47+
- [x] Add clear error messages
48+
- [x] Update documentation
49+
- [x] Schema documentation in low-level design
50+
- [x] Example configuration in design docs
51+
3852
### ConfigMetadata
3953
- [x] Implement ConfigMetadata class
4054
- [x] Add metadata attributes
@@ -45,21 +59,6 @@
4559
- [x] Implement from_dict() static method
4660
- [x] Add tests for serialization/deserialization
4761

48-
### HelmValuesConfig Refactoring
49-
- [ ] Remove PlainTextBackend references
50-
- [ ] Update imports and dependencies
51-
- [ ] Remove plaintext.py
52-
- [ ] Update tests
53-
- [ ] Implement unified path storage
54-
- [ ] Add _path_map dictionary
55-
- [ ] Migrate existing code to use _path_map
56-
- [ ] Update tests for new structure
57-
- [ ] Update value management
58-
- [ ] Refactor set_value() to use Value class
59-
- [ ] Refactor get_value() to use Value class
60-
- [ ] Add value validation in set operations
61-
- [ ] Update tests for new value handling
62-
6362
### Backend System
6463
- [ ] Clean up base ValueBackend
6564
- [ ] Update interface methods
@@ -97,7 +96,6 @@
9796
- [x] Value class tests
9897
- [x] PathData class tests
9998
- [x] ConfigMetadata tests
100-
- [ ] HelmValuesConfig tests
10199
- [ ] Backend tests
102100
- [ ] Command tests
103101
- [ ] Add integration tests

helm_values_manager/backends/base.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from abc import ABC, abstractmethod
8-
from typing import Dict
8+
from typing import Dict, Union
99

1010

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

6262
@abstractmethod
63-
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
63+
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
6464
"""
65-
Get a value from storage.
65+
Get a value from the backend.
6666
6767
Args:
68-
path: The configuration path
69-
environment: The environment name
70-
resolve: If True, resolve any secret references to their actual values.
71-
If False, return the raw value which may be a secret reference.
68+
path: The configuration path (e.g., "app.replicas")
69+
environment: The environment name (e.g., "dev", "prod")
70+
resolve: Whether to resolve any secret references
7271
7372
Returns:
74-
str: The value (resolved or raw depending on resolve parameter)
73+
The value from the backend, can be a string, number, boolean, or None
74+
75+
Raises:
76+
ValueError: If the value doesn't exist
77+
RuntimeError: If backend operation fails
7578
"""
7679
pass
7780

7881
@abstractmethod
79-
def set_value(self, path: str, environment: str, value: str) -> None:
80-
"""Set a value in the storage backend.
82+
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
83+
"""
84+
Set a value in the backend.
8185
8286
Args:
8387
path: The configuration path (e.g., "app.replicas")
8488
environment: The environment name (e.g., "dev", "prod")
85-
value: The value to store. Must be a string.
89+
value: The value to store, can be a string, number, boolean, or None
8690
8791
Raises:
88-
ValueError: If the value is invalid
89-
ConnectionError: If there's an error connecting to the backend
90-
PermissionError: If there's an authentication or authorization error
92+
ValueError: If the value is not a string, number, boolean, or None
93+
RuntimeError: If backend operation fails
9194
"""
9295
pass
9396

helm_values_manager/backends/simple.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
This module provides a simple in-memory backend for storing non-sensitive values.
55
"""
66

7-
from typing import Dict
7+
from typing import Dict, Union
88

99
from helm_values_manager.backends.base import ValueBackend
1010

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

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

28-
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
28+
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
2929
"""
3030
Get a value from the in-memory storage.
3131
3232
Args:
3333
path: The configuration path (e.g., "app.replicas")
3434
environment: The environment name (e.g., "dev", "prod")
35-
resolve: If True, resolve any secret references to their actual values.
36-
If False, return the raw value which may be a secret reference.
35+
resolve: Whether to resolve any secret references
3736
3837
Returns:
39-
str: The value (resolved or raw depending on resolve parameter)
38+
The value from the backend, can be a string, number, boolean, or None
4039
4140
Raises:
4241
ValueError: If the value doesn't exist
@@ -46,20 +45,21 @@ def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
4645
raise ValueError(f"No value found for {path} in {environment}")
4746
return self._storage[key]
4847

49-
def set_value(self, path: str, environment: str, value: str) -> None:
48+
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
5049
"""
5150
Set a value in the in-memory storage.
5251
5352
Args:
5453
path: The configuration path (e.g., "app.replicas")
5554
environment: The environment name (e.g., "dev", "prod")
56-
value: The value to store
55+
value: The value to store, can be a string, number, boolean, or None
5756
5857
Raises:
59-
ValueError: If the value is not a string
58+
ValueError: If the value is not a string, number, boolean, or None
6059
"""
61-
if not isinstance(value, str):
62-
raise ValueError("Value must be a string")
60+
if not isinstance(value, (str, int, float, bool, type(None))):
61+
raise ValueError("Value must be a string, number, boolean, or None")
62+
6363
key = self._get_storage_key(path, environment)
6464
self._storage[key] = value
6565

0 commit comments

Comments
 (0)