Skip to content

Commit a31c758

Browse files
committed
feat: implement value storage backend system
Partial implementation of #4 Core changes: - Add ValueBackend abstract base class with key-value interface - Add PlainTextBackend for in-memory storage - Add comprehensive unit test suite Documentation: - Update ADR with value storage model and design rationale - Enhance low-level design with implementation details - Update architecture overview with storage components Testing: - Set up pytest configuration - Add unit test structure with backend tests - Add test coverage for base and plaintext backends This change introduces a clean separation between configuration management and value storage, using a simple key-value interface that aligns well with cloud provider APIs and enables easy testing. Part of: #4
1 parent ee69d10 commit a31c758

File tree

11 files changed

+376
-17
lines changed

11 files changed

+376
-17
lines changed

docs/ADRs/001-helm-values-manager.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,28 @@ We have decided to implement the **Helm Values Manager** as a **Helm plugin writ
2525
7. **ArgoCD Compatibility:** Generates `values.json` dynamically for GitOps workflows.
2626
8. **JSON for Configuration:** Using JSON for configuration files provides better schema validation and consistent parsing across different platforms.
2727

28+
### Value Storage Model
29+
The system uses a key-value storage model with clean separation of concerns:
30+
1. **Configuration Layer**: Manages paths and environments, generating unique keys
31+
2. **Storage Layer**: Simple key-value interface for all backend implementations
32+
33+
This design:
34+
- Simplifies backend implementations
35+
- Better aligns with cloud provider secret managers
36+
- Provides flexibility in key generation strategies
37+
- Enables easier testing and mocking
38+
39+
For detailed implementation, see [Low-Level Design](../Design/low-level-design.md).
40+
2841
## Configuration Structure
2942

43+
While the configuration file uses a hierarchical structure with paths and environments, internally values are stored using a key-value model. This provides:
44+
- Simpler backend implementations
45+
- Better alignment with secret manager APIs
46+
- Flexibility in key generation strategies
47+
48+
See the [Low-Level Design](../Design/low-level-design.md) for implementation details.
49+
3050
The configuration follows this structure:
3151

3252
```json

docs/Design/architecture-overview.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Helm Values Manager simplifies **configuration and secret management** across mu
88
The Helm plugin consists of:
99
- **CLI Command Interface (Python Typer-based)**: Handles command execution.
1010
- **Validation Engine**: Ensures required values have values for each deployment.
11-
- **Value Storage Backend**: Supports AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, and Git-Secrets.
11+
- **Configuration Manager**: Manages path/environment organization and key generation.
12+
- **Value Storage Backend**: Provides key-value storage through AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, and Git-Secrets.
1213
- **values.yaml Generator**: Produces the final Helm-compatible values file.
1314
- **Helm Plugin System**: Integrates seamlessly with Helm commands.
1415
- **JSON Schema Validation**: Ensures configuration files follow the correct structure.
@@ -57,12 +58,12 @@ from abc import ABC, abstractmethod
5758

5859
class ValueBackend(ABC):
5960
@abstractmethod
60-
def get_value(self, path: str, environment: str) -> str:
61+
def get_value(self, key: str) -> str:
6162
"""Get a value from the secrets backend."""
6263
pass
6364

6465
@abstractmethod
65-
def set_value(self, path: str, environment: str, value: str) -> None:
66+
def set_value(self, key: str, value: str) -> None:
6667
"""Set a value in the secrets backend."""
6768
pass
6869
```

docs/Design/low-level-design.md

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,35 +38,37 @@ classDiagram
3838

3939
### Value Storage
4040

41-
The value storage system follows a clean separation between the domain model and storage backends:
41+
The value storage system follows a clean separation between the domain model and storage backends. The key design principle is separation of concerns:
42+
1. `HelmValuesConfig` handles the organization of values (paths and environments)
43+
2. `ValueBackend` focuses purely on key-value storage
4244

4345
```mermaid
4446
classDiagram
4547
class ValueBackend {
4648
<<interface>>
47-
+get_value(path: str, environment: str)* str
48-
+set_value(path: str, environment: str, value: str)* None
49+
+get_value(key: str)* str
50+
+set_value(key: str, value: str)* None
4951
+validate_auth_config(auth_config: dict)* None
5052
}
5153
5254
class PlainTextBackend {
5355
-Path values_file
54-
+get_value(path: str, environment: str) str
55-
+set_value(path: str, environment: str, value: str) None
56+
+get_value(key: str) str
57+
+set_value(key: str, value: str) None
5658
+validate_auth_config(auth_config: dict) None
5759
}
5860
5961
class AWSSecretsBackend {
6062
-SecretsManagerClient client
61-
+get_value(path: str, environment: str) str
62-
+set_value(path: str, environment: str, value: str) None
63+
+get_value(key: str) str
64+
+set_value(key: str, value: str) None
6365
+validate_auth_config(auth_config: dict) None
6466
}
6567
6668
class AzureKeyVaultBackend {
6769
-KeyVaultClient client
68-
+get_value(path: str, environment: str) str
69-
+set_value(path: str, environment: str, value: str) None
70+
+get_value(key: str) str
71+
+set_value(key: str, value: str) None
7072
+validate_auth_config(auth_config: dict) None
7173
}
7274
@@ -75,6 +77,50 @@ classDiagram
7577
ValueBackend <|.. AzureKeyVaultBackend
7678
```
7779

80+
Key responsibilities:
81+
82+
1. **HelmValuesConfig**:
83+
- Maintains the path/environment organization
84+
- Generates unique keys for the backend
85+
- Maps keys back to path/environment structure
86+
```python
87+
class HelmValuesConfig:
88+
def _generate_key(self, path: str, environment: str) -> str:
89+
# Could use various strategies
90+
return f"{path}:{environment}" # Simple concatenation
91+
# or
92+
# return hashlib.sha256(f"{path}:{environment}".encode()).hexdigest()
93+
94+
def get_value(self, path: str, environment: str) -> str:
95+
deployment = self._get_deployment(environment)
96+
backend = self._create_backend(deployment)
97+
key = self._generate_key(path, environment)
98+
return backend.get_value(key)
99+
```
100+
101+
2. **ValueBackend**:
102+
- Focuses purely on key-value storage
103+
- Handles storage-specific authentication
104+
- Manages storage operations and error handling
105+
```python
106+
class ValueBackend(ABC):
107+
@abstractmethod
108+
def get_value(self, key: str) -> str:
109+
"""Get a value from storage using a unique key."""
110+
pass
111+
112+
@abstractmethod
113+
def set_value(self, key: str, value: str) -> None:
114+
"""Store a value using a unique key."""
115+
pass
116+
```
117+
118+
This design provides several benefits:
119+
1. **Clean Separation**: Each component has a single responsibility
120+
2. **Cloud Provider Alignment**: Better matches cloud secret manager APIs
121+
3. **Simplified Backend Implementation**: Reduces complexity in backends
122+
4. **Future Extensibility**: Easy to add new organizational schemes
123+
78124
### Configuration Flow
79125

80126
The configuration flow shows how data moves through the system:
@@ -100,7 +146,8 @@ sequenceDiagram
100146
HelmValuesConfig->>ValueBackend: create_backend(deployment)
101147
activate ValueBackend
102148
ValueBackend->>ValueBackend: validate_auth_config()
103-
ValueBackend->>Storage: read_value(path, env)
149+
HelmValuesConfig->>HelmValuesConfig: generate_key(path, env)
150+
ValueBackend->>Storage: read_value(key)
104151
Storage-->>ValueBackend: value
105152
ValueBackend-->>HelmValuesConfig: value
106153
deactivate ValueBackend
@@ -111,7 +158,8 @@ sequenceDiagram
111158
HelmValuesConfig->>HelmValuesConfig: validate_value(value)
112159
HelmValuesConfig->>ValueBackend: create_backend(deployment)
113160
activate ValueBackend
114-
ValueBackend->>Storage: write_value(path, env, value)
161+
HelmValuesConfig->>HelmValuesConfig: generate_key(path, env)
162+
ValueBackend->>Storage: write_value(key, value)
115163
Storage-->>ValueBackend: success
116164
ValueBackend-->>HelmValuesConfig: success
117165
deactivate ValueBackend
@@ -158,7 +206,7 @@ This flow diagram shows:
158206
backend = self._create_backend(deployment)
159207

160208
# Get the value through the backend
161-
return backend.get_value(path, environment)
209+
return backend.get_value(self._generate_key(path, environment))
162210
```
163211

164212
### Backend Implementation
@@ -167,11 +215,11 @@ This flow diagram shows:
167215
```python
168216
class ValueBackend(ABC):
169217
@abstractmethod
170-
def get_value(self, path: str, environment: str) -> str:
218+
def get_value(self, key: str) -> str:
171219
pass
172220

173221
@abstractmethod
174-
def set_value(self, path: str, environment: str, value: str) -> None:
222+
def set_value(self, key: str, value: str) -> None:
175223
pass
176224

177225
@abstractmethod
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Value storage backends for helm-values-manager."""
2+
3+
from .base import ValueBackend
4+
from .plain import PlainTextBackend
5+
6+
__all__ = ["ValueBackend", "PlainTextBackend"]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Base module for value storage backends.
2+
3+
This module provides the abstract base class for all value storage backends.
4+
Each backend must implement the key-value interface defined here.
5+
"""
6+
7+
from abc import ABC, abstractmethod
8+
9+
10+
class ValueBackend(ABC):
11+
"""Abstract base class for value storage backends.
12+
13+
This class defines the interface that all value storage backends must implement.
14+
Implementations can store values in memory, secret managers, or other storage systems.
15+
The backend operates on a simple key-value model.
16+
"""
17+
18+
def __init__(self, auth_config: dict):
19+
"""Initialize the backend with authentication configuration.
20+
21+
Args:
22+
auth_config: Optional authentication configuration
23+
"""
24+
self._validate_auth_config(auth_config)
25+
26+
def _validate_auth_config(self, auth_config: dict) -> None:
27+
"""Validate the authentication configuration.
28+
29+
This is an internal method called during initialization.
30+
Subclasses should override this to implement their specific validation.
31+
32+
Args:
33+
auth_config: The authentication configuration to validate
34+
"""
35+
if auth_config is None or not isinstance(auth_config, dict):
36+
raise ValueError("Auth config must be a dictionary")
37+
38+
if "type" not in auth_config:
39+
raise ValueError("Auth config must contain 'type'")
40+
41+
if auth_config["type"] not in ["env", "file", "direct", "managed_identity"]:
42+
raise ValueError(f"Invalid auth type: {auth_config['type']}")
43+
44+
@abstractmethod
45+
def get_value(self, key: str) -> str:
46+
"""Get a value from the storage backend.
47+
48+
Args:
49+
key: The unique key to retrieve the value for
50+
51+
Returns:
52+
The value stored in the backend
53+
54+
Raises:
55+
ValueError: If the key doesn't exist
56+
"""
57+
pass
58+
59+
@abstractmethod
60+
def set_value(self, key: str, value: str) -> None:
61+
"""Set a value in the storage backend.
62+
63+
Args:
64+
key: The unique key to store the value under
65+
value: The value to store
66+
"""
67+
pass
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""PlainText backend for value storage.
2+
3+
This module provides a simple in-memory key-value store implementation.
4+
It is suitable for development and testing environments.
5+
"""
6+
7+
from typing import Dict
8+
9+
from .base import ValueBackend
10+
11+
12+
class PlainTextBackend(ValueBackend):
13+
"""A backend that stores values in memory.
14+
15+
This backend is suitable for development and testing environments.
16+
Values are stored in memory and will be lost when the process exits.
17+
"""
18+
19+
def __init__(self, auth_config: dict = None):
20+
"""Initialize the backend.
21+
22+
Args:
23+
auth_config: Optional authentication configuration (not used)
24+
"""
25+
self._values: Dict[str, str] = {}
26+
super().__init__(auth_config)
27+
28+
def _validate_auth_config(self, auth_config: dict) -> None:
29+
"""Validate the authentication configuration.
30+
31+
For PlainTextBackend, no authentication is required.
32+
Any auth config will pass validation.
33+
34+
Args:
35+
auth_config: The authentication configuration to validate
36+
"""
37+
pass # No validation needed for plain text backend
38+
39+
def get_value(self, key: str) -> str:
40+
"""Get a value from memory.
41+
42+
Args:
43+
key: The key to retrieve the value for
44+
45+
Returns:
46+
The stored value
47+
48+
Raises:
49+
ValueError: If the key doesn't exist
50+
"""
51+
if key not in self._values:
52+
raise ValueError(f"Key not found: {key}")
53+
54+
return self._values[key]
55+
56+
def set_value(self, key: str, value: str) -> None:
57+
"""Set a value in memory.
58+
59+
Args:
60+
key: The key to store the value under
61+
value: The value to store
62+
"""
63+
self._values[key] = value

pytest.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pytest]
2+
testpaths = tests
3+
python_files = test_*.py
4+
python_classes = Test*
5+
python_functions = test_*

tests/unit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Unit tests for the helm-values-manager package."""

tests/unit/backends/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Unit tests for the value storage backends."""

0 commit comments

Comments
 (0)