Skip to content

Commit 034a4d4

Browse files
authored
Merge pull request #12 from Zipstack/4-metadata-refactor
refactor: update metadata handling and improve code organization
2 parents caa1b62 + be1d1e1 commit 034a4d4

File tree

16 files changed

+419
-217
lines changed

16 files changed

+419
-217
lines changed

docs/ADRs/005-unified-backend-approach.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# ADR-005: Unified Backend Approach for Value Storage
22

33
## Status
4-
Proposed
4+
Accepted
55

66
## Context
77
Currently, the Value class handles two distinct storage types: local and remote. This creates a split in logic within the Value class, requiring different code paths and validation rules based on the storage type. This complexity makes the code harder to maintain and test.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ADR 007: Sensitive Value Storage
2+
3+
## Status
4+
Accepted
5+
6+
## Context
7+
The helm-values-manager needs to handle both sensitive and non-sensitive configuration values. While non-sensitive values can be stored directly in the configuration files, sensitive values require special handling to ensure security.
8+
9+
## Decision
10+
We will store sensitive values using the existing configuration structure, with sensitive values using a special reference format.
11+
The existing schema already supports this through its `sensitive` flag and flexible string values.
12+
13+
1. When a value is marked as sensitive (`sensitive: true`):
14+
- The actual value will be stored in a secure backend (AWS Secrets Manager, Azure Key Vault, etc.)
15+
- Only a reference to the secret will be stored in our configuration files
16+
- The reference will use a URI-like format: `secret://<backend-type>/<secret-path>`
17+
18+
2. Example configuration showing both sensitive and non-sensitive values:
19+
```json
20+
{
21+
"version": "1.0",
22+
"release": "my-app",
23+
"deployments": {
24+
"prod": {
25+
"backend": "gcp",
26+
"auth": {
27+
"type": "managed_identity"
28+
},
29+
"backend_config": {
30+
"region": "us-central1"
31+
}
32+
}
33+
},
34+
"config": [
35+
{
36+
"path": "app.replicas",
37+
"description": "Number of application replicas",
38+
"required": true,
39+
"sensitive": false,
40+
"values": {
41+
"dev": "3",
42+
"prod": "5"
43+
}
44+
},
45+
{
46+
"path": "app.database.password",
47+
"description": "Database password",
48+
"required": true,
49+
"sensitive": true,
50+
"values": {
51+
"dev": "secret://gcp-secrets/my-app/dev/db-password",
52+
"prod": "secret://gcp-secrets/my-app/prod/db-password"
53+
}
54+
}
55+
]
56+
}
57+
```
58+
59+
This approach:
60+
1. Leverages the existing schema without modifications:
61+
- Uses the `sensitive` flag to identify sensitive values
62+
- Uses the flexible string type in `values` to store references
63+
- Maintains backward compatibility
64+
2. Provides a clear and consistent format for secret references
65+
3. Supports versioning through the secret path (e.g., `secret://gcp-secrets/my-app/prod/db-password/v1`)
66+
67+
The validation and resolution of secret references will be handled by:
68+
1. The backend implementation (parsing and resolving references)
69+
2. The `Value` class (determining whether to treat a value as a reference based on the `sensitive` flag)
70+
71+
## Implementation Notes
72+
1. Secret references will be parsed and validated by the backend implementation
73+
2. The `Value` class will check the `sensitive` flag to determine how to handle the value:
74+
- If `sensitive: false`, use the value as-is
75+
- If `sensitive: true`, parse the value as a secret reference and resolve it
76+
3. Each secure backend will implement its own reference resolution logic
77+
4. Future enhancement: Add commands to manage secrets directly through the tool
78+
79+
## Consequences
80+
81+
### Positive
82+
- Security: Sensitive values never leave the secure backend
83+
- Traceability: Easy to track which secrets are used where
84+
- Versioning: Support for secret rotation via version references
85+
- Flexibility: Different backends can implement their own reference formats
86+
- Auditability: References are human-readable for easier debugging
87+
88+
### Negative
89+
- Additional Setup: Users need to create secrets separately (until we add direct creation support)
90+
- Complexity: Need to manage both direct values and secret references
91+
- Dependencies: Requires access to secure backends
92+
93+
## Related
94+
- ADR 0001 (if it exists, about general value storage)
95+
- Future ADR about secret management commands

docs/ADRs/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ An Architecture Decision Record (ADR) is a document that captures an important a
3535
- **Dependencies**: ADR-003
3636

3737
### [ADR-005: Unified Backend Approach](005-unified-backend-approach.md)
38-
- **Status**: Proposed
38+
- **Status**: Accepted
3939
- **Context**: Split logic in Value class for different storage types
4040
- **Decision**: Remove storage type distinction, use SimpleValueBackend
4141
- **Impact**: Simplifies Value class and unifies storage interface
@@ -48,6 +48,13 @@ An Architecture Decision Record (ADR) is a document that captures an important a
4848
- **Impact**: Ensures consistent user experience and debugging
4949
- **Dependencies**: ADR-001
5050

51+
### [ADR-007: Sensitive Value Storage](007-sensitive-value-storage.md)
52+
- **Status**: Accepted
53+
- **Context**: Need for secure handling of sensitive configuration values
54+
- **Decision**: Store sensitive values using reference-based approach in secure backends
55+
- **Impact**: Ensures security while maintaining flexibility and traceability
56+
- **Dependencies**: ADR-005
57+
5158
## ADR Template
5259
For new ADRs, use this template:
5360
```markdown

docs/Design/low-level-design.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ classDiagram
2323
class PathData {
2424
+str path
2525
+Dict metadata
26-
+Dict~str,Value~ values
26+
-Dict~str,Value~ _values
2727
+validate() None
28-
+add_value(environment: str, value: Value) None
28+
+set_value(environment: str, value: Value) None
2929
+get_value(environment: str) Optional[Value]
30+
+get_environments() Iterator[str]
3031
+to_dict() Dict
3132
+from_dict(data: Dict, create_value_fn) PathData
3233
}
@@ -212,12 +213,12 @@ The configuration follows the v1 schema:
212213
"release": "my-app",
213214
"deployments": {
214215
"prod": {
215-
"backend": "aws",
216+
"backend": "gcp",
216217
"auth": {
217218
"type": "managed_identity"
218219
},
219220
"backend_config": {
220-
"region": "us-west-2"
221+
"region": "us-central1"
221222
}
222223
}
223224
},
@@ -231,6 +232,16 @@ The configuration follows the v1 schema:
231232
"dev": "3",
232233
"prod": "5"
233234
}
235+
},
236+
{
237+
"path": "app.database.password",
238+
"description": "Database password",
239+
"required": true,
240+
"sensitive": true,
241+
"values": {
242+
"dev": "secret://gcp-secrets/my-app/dev/db-password",
243+
"prod": "secret://gcp-secrets/my-app/prod/db-password"
244+
}
234245
}
235246
]
236247
}

docs/Development/tasks.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@
3030
- [x] Implement to_dict() method
3131
- [x] Implement from_dict() method
3232
- [x] Add tests for serialization
33+
- [x] Add metadata handling
34+
- [x] Create ConfigMetadata class
35+
- [x] Add tests for ConfigMetadata
36+
- [x] Integrate with PathData
37+
38+
### ConfigMetadata
39+
- [x] Implement ConfigMetadata class
40+
- [x] Add metadata attributes
41+
- [x] Implement constructor with type hints
42+
- [x] Add basic validation for attributes
43+
- [x] Add serialization support
44+
- [x] Implement to_dict() method
45+
- [x] Implement from_dict() static method
46+
- [x] Add tests for serialization/deserialization
3347

3448
### HelmValuesConfig Refactoring
3549
- [ ] Remove PlainTextBackend references
@@ -82,6 +96,7 @@
8296
- [x] Add unit tests
8397
- [x] Value class tests
8498
- [x] PathData class tests
99+
- [x] ConfigMetadata tests
85100
- [ ] HelmValuesConfig tests
86101
- [ ] Backend tests
87102
- [ ] Command tests
@@ -110,6 +125,7 @@
110125
- [ ] Update API documentation
111126
- [ ] Document Value class
112127
- [ ] Document PathData class
128+
- [ ] Document ConfigMetadata class
113129
- [ ] Update HelmValuesConfig docs
114130
- [ ] Add usage examples
115131
- [ ] Basic usage examples

helm_values_manager/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
different storage systems like AWS Secrets Manager or Azure Key Vault.
66
"""
77

8-
from .base import ValueBackend
8+
from helm_values_manager.backends.base import ValueBackend
99

1010
__all__ = ["ValueBackend"]

helm_values_manager/backends/base.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(self, auth_config: Dict[str, str]) -> None:
3535
ValueError: If the auth_config is invalid
3636
"""
3737
self._validate_auth_config(auth_config)
38+
self.backend_type = self.__class__.__name__.lower().replace("backend", "")
3839

3940
def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
4041
"""Validate the authentication configuration.
@@ -59,20 +60,18 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
5960
raise ValueError(f"Invalid auth type: {auth_config['type']}. " f"Must be one of: {', '.join(valid_types)}")
6061

6162
@abstractmethod
62-
def get_value(self, path: str, environment: str) -> str:
63-
"""Get a value from the storage backend.
63+
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
64+
"""
65+
Get a value from storage.
6466
6567
Args:
66-
path: The configuration path (e.g., "app.replicas")
67-
environment: The environment name (e.g., "dev", "prod")
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.
6872
6973
Returns:
70-
str: The value stored in the backend.
71-
72-
Raises:
73-
ValueError: If the value doesn't exist
74-
ConnectionError: If there's an error connecting to the backend
75-
PermissionError: If there's an authentication or authorization error
74+
str: The value (resolved or raw depending on resolve parameter)
7675
"""
7776
pass
7877

helm_values_manager/backends/simple.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from typing import Dict
88

9-
from .base import ValueBackend
9+
from helm_values_manager.backends.base import ValueBackend
1010

1111

1212
class SimpleValueBackend(ValueBackend):
@@ -25,16 +25,18 @@ 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) -> str:
28+
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
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.
3537
3638
Returns:
37-
str: The stored value
39+
str: The value (resolved or raw depending on resolve parameter)
3840
3941
Raises:
4042
ValueError: If the value doesn't exist
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Models package for helm-values-manager."""
22

3-
from .value import Value
3+
from helm_values_manager.models.value import Value
44

55
__all__ = ["Value"]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Simple dataclass for configuration metadata."""
2+
3+
from dataclasses import asdict, dataclass
4+
from typing import Any, Dict, Optional
5+
6+
7+
@dataclass
8+
class ConfigMetadata:
9+
"""
10+
Represents metadata for a configuration path.
11+
12+
Attributes:
13+
description (Optional[str]): Description of the configuration path.
14+
required (bool): Whether the configuration path is required. Defaults to False.
15+
sensitive (bool): Whether the configuration path is sensitive. Defaults to False.
16+
"""
17+
18+
description: Optional[str] = None
19+
required: bool = False
20+
sensitive: bool = False
21+
22+
def to_dict(self) -> Dict[str, Any]:
23+
"""Convert metadata to dictionary."""
24+
return asdict(self)
25+
26+
@classmethod
27+
def from_dict(cls, data: Dict[str, Any]) -> "ConfigMetadata":
28+
"""Create a ConfigMetadata instance from a dictionary."""
29+
return cls(
30+
description=data.get("description"),
31+
required=data.get("required", False),
32+
sensitive=data.get("sensitive", False),
33+
)

0 commit comments

Comments
 (0)