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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ ipython_config.py
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Helm Values Manager specific
.helm-values.lock

# Celery stuff
celerybeat-schedule
celerybeat.pid
Expand Down
92 changes: 82 additions & 10 deletions docs/Development/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,90 @@
- [ ] Add better validation

### Command System
- [ ] Implement BaseCommand improvements
- [ ] Add file locking mechanism
- [ ] Implement backup strategy
- [ ] Add better error handling
- [ ] Add new commands

#### Phase 1: Core Infrastructure & Essential Commands
- [x] Basic Command Framework
- [x] Create commands directory structure
- [x] Implement BaseCommand class with basic flow
- [x] Add configuration loading/saving
- [x] Add error handling and logging
- [ ] Add command registration in CLI
- [ ] Add basic command discovery

- [ ] Configuration Setup Commands
- [ ] Implement init command
- [ ] Add empty config initialization
- [ ] Add config file creation
- [ ] Add schema template generation
- [ ] Implement add-value-config command
- [ ] Add basic path validation
- [ ] Add metadata validation
- [ ] Add config update
- [ ] Implement add-deployment command
- [ ] Add basic deployment validation
- [ ] Add backend validation
- [ ] Add deployment registration
- [ ] Implement generate command
- [ ] Add template generation
- [ ] Add basic value substitution

- [ ] Value Management Commands
- [ ] Implement get-value command
- [ ] Add basic path validation
- [ ] Add value retrieval
- [ ] Implement set-value command
- [ ] Add basic path validation
- [ ] Add value storage

#### Phase 2: Enhanced Safety & Management
- [ ] Enhanced Command Infrastructure
- [ ] Add file locking mechanism
- [ ] Add atomic writes
- [ ] Add basic backup strategy

- [ ] Configuration Management
- [ ] Implement remove-value-config command
- [ ] Update existing commands for new structure
- [ ] Update command validation
- [ ] Add input validation
- [ ] Improve error messages
- [ ] Add command-specific validation
- [ ] Add path validation
- [ ] Add basic cleanup
- [ ] Enhance add-value-config
- [ ] Add conflict detection
- [ ] Add dependency validation

- [ ] Basic Validation System
- [ ] Add PathValidator class
- [ ] Add path format validation
- [ ] Add existence checks

#### Phase 3: Advanced Features
- [ ] Enhanced Security & Recovery
- [ ] Add comprehensive backup strategy
- [ ] Add rollback support
- [ ] Improve error handling

- [ ] Deployment Management
- [ ] Add DeploymentValidator class

- [ ] Advanced Validation
- [ ] Add ValueValidator class
- [ ] Add conflict detection
- [ ] Add dependency checking

#### Phase 4: Polish & Documentation
- [ ] Command Documentation
- [ ] Add command documentation generation
- [ ] Add help text improvements
- [ ] Add usage examples

- [ ] Testing Infrastructure
- [ ] Add command test fixtures
- [ ] Add mock file system
- [ ] Add mock backend
- [ ] Add integration tests

- [ ] Final Touches
- [ ] Add command output formatting
- [ ] Add progress indicators
- [ ] Add interactive mode support

### Testing Infrastructure
- [x] Set up test infrastructure
Expand Down
1 change: 1 addition & 0 deletions helm_values_manager/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Commands package containing command implementations for the helm-values-manager."""
150 changes: 150 additions & 0 deletions helm_values_manager/commands/base_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""Base command class for helm-values plugin."""

import fcntl
import json
import os
from typing import Any, Optional

from jsonschema.exceptions import ValidationError

from helm_values_manager.models.helm_values_config import HelmValuesConfig
from helm_values_manager.utils.logger import HelmLogger


class BaseCommand:
"""Base class for all helm-values commands.

This class provides common functionality for all commands including:
- Configuration loading and saving
- Error handling and logging
- Lock management for concurrent access
"""

def __init__(self) -> None:
"""Initialize the base command."""
self.config_file = "helm-values.json"
self.lock_file = ".helm-values.lock"
self._lock_fd: Optional[int] = None

def _load_config_file(self) -> dict:
"""Load and parse the configuration file.

Returns:
dict: The parsed configuration data

Raises:
FileNotFoundError: If the config file doesn't exist
json.JSONDecodeError: If the file contains invalid JSON
"""
if not os.path.exists(self.config_file):
HelmLogger.error("Configuration file %s not found", self.config_file)
raise FileNotFoundError(f"Configuration file {self.config_file} not found")

try:
with open(self.config_file, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError as e:
HelmLogger.error("Failed to parse configuration file: %s", e)
raise

def load_config(self) -> HelmValuesConfig:
"""Load the helm-values configuration from disk.

Returns:
HelmValuesConfig: The loaded configuration.

Raises:
FileNotFoundError: If the config file doesn't exist.
json.JSONDecodeError: If the file contains invalid JSON.
ValidationError: If the configuration format is invalid.
"""
data = self._load_config_file()
try:
return HelmValuesConfig.from_dict(data)
except ValidationError as e:
HelmLogger.error("Invalid configuration format: %s", e)
raise

def save_config(self, config: HelmValuesConfig) -> None:
"""Save the helm-values configuration to disk.

Args:
config: The configuration to save.

Raises:
IOError: If unable to write to the file.
"""
try:
with open(self.config_file, "w", encoding="utf-8") as f:
json.dump(config.to_dict(), f, indent=2)
except IOError as e:
HelmLogger.error("Failed to save configuration: %s", e)
raise

def acquire_lock(self) -> None:
"""Acquire an exclusive lock for file operations.

This ensures thread-safe operations when multiple commands
are trying to modify the configuration.

Raises:
IOError: If unable to acquire the lock.
"""
self._lock_fd = os.open(self.lock_file, os.O_CREAT | os.O_RDWR)
try:
fcntl.flock(self._lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
HelmLogger.debug("Acquired lock on file %s", self.lock_file)
except IOError:
os.close(self._lock_fd)
self._lock_fd = None
HelmLogger.error("Unable to acquire lock. Another command may be running.")
raise IOError("Unable to acquire lock. Another command may be running.")

def release_lock(self) -> None:
"""Release the exclusive lock."""
if self._lock_fd is not None:
fcntl.flock(self._lock_fd, fcntl.LOCK_UN)
os.close(self._lock_fd)
self._lock_fd = None
HelmLogger.debug("Released lock on file %s", self.lock_file)

def execute(self) -> Any:
"""Execute the command.

This is the main entry point for running a command.
It handles:
1. Lock acquisition
2. Configuration loading
3. Command execution via run()
4. Lock release

Returns:
Any: The result of the command execution.

Raises:
Exception: If any error occurs during command execution.
"""
try:
self.acquire_lock()
config = self.load_config()
result = self.run(config)
return result
finally:
self.release_lock()

def run(self, config: HelmValuesConfig) -> Any:
"""Run the command-specific logic.

This method should be implemented by each specific command subclass
to perform its unique functionality.

Args:
config: The loaded configuration.

Returns:
Any: The result of running the command.

Raises:
NotImplementedError: This method must be implemented by subclasses.
"""
raise NotImplementedError("Subclasses must implement run()")
1 change: 1 addition & 0 deletions tests/unit/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests for the commands package."""
Loading