Add rollback restoration for failed deployments#794
Conversation
Reviewer's GuideThis PR enhances the DeploymentAutomator to support automatic rollback by tracking and restoring from the most recent backup when a deployment fails. Sequence diagram for deployment with automatic rollback restorationsequenceDiagram
participant User
participant DeploymentAutomator
User->>DeploymentAutomator: deploy(environment, mode)
DeploymentAutomator->>DeploymentAutomator: _create_backup()
DeploymentAutomator->>DeploymentAutomator: last_backup = backup_name
DeploymentAutomator->>DeploymentAutomator: deployment_plan["backup_name"] = last_backup
alt Deployment fails
DeploymentAutomator->>DeploymentAutomator: _rollback_deployment(deployment_plan)
alt last_backup exists
DeploymentAutomator->>DeploymentAutomator: deployment_plan["rollback_backup"] = last_backup
DeploymentAutomator->>DeploymentAutomator: _restore_backup(last_backup)
else No backup available
DeploymentAutomator->>DeploymentAutomator: print("No backup available for rollback")
end
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Pull Request Overview
This PR implements automatic backup restoration for failed deployments by tracking the most recent backup and adding restoration capabilities to the rollback process.
- Tracks the most recent backup in the
DeploymentAutomatorclass instance - Adds a
_restore_backuphelper method to restore workspace from backups - Records the backup name in deployment plans and performs automatic restoration during rollbacks
| self.deployment_dir = self.workspace_root / "09-deployment" | ||
| self.config_dir = self.workspace_root / "10-config" | ||
| self.backup_dir = self.workspace_root / "backups" | ||
| self.last_backup: Optional[str] = None |
There was a problem hiding this comment.
The last_backup attribute lacks documentation. Consider adding a docstring or inline comment explaining its purpose and when it gets updated.
| self.last_backup: Optional[str] = None | |
| self.last_backup: Optional[str] = None # Stores the name of the most recent backup created during deployment. Updated by the `_create_backup` method. |
| src = backup_path / rel_path | ||
| dest = self.workspace_root / rel_path | ||
|
|
||
| if dest.exists(): |
There was a problem hiding this comment.
The restoration process removes existing files/directories without validation. This could lead to unintended data loss if the backup contains malicious paths or if there's a path traversal vulnerability. Consider validating that rel_path doesn't contain directory traversal sequences like '../'.
| if src.is_file(): | ||
| shutil.copy2(src, dest) | ||
| else: |
There was a problem hiding this comment.
The code attempts to copy files without ensuring the parent directories exist. If the destination directory structure doesn't exist, shutil.copy2 will fail. Consider using dest.parent.mkdir(parents=True, exist_ok=True) before copying.
| if src.is_file(): | |
| shutil.copy2(src, dest) | |
| else: | |
| if src.is_file(): | |
| dest.parent.mkdir(parents=True, exist_ok=True) | |
| shutil.copy2(src, dest) | |
| else: | |
| dest.parent.mkdir(parents=True, exist_ok=True) |
| if self.last_backup: | ||
| deployment_plan["rollback_backup"] = self.last_backup | ||
| self._restore_backup(self.last_backup) |
There was a problem hiding this comment.
The rollback logic uses self.last_backup but also checks for deployment_plan.get('backup_name') would be more reliable since it's explicitly tied to the current deployment. Consider using the backup name from the deployment plan instead of the instance variable.
| if self.last_backup: | |
| deployment_plan["rollback_backup"] = self.last_backup | |
| self._restore_backup(self.last_backup) | |
| backup_name = deployment_plan.get("backup_name") | |
| if backup_name: | |
| deployment_plan["rollback_backup"] = backup_name | |
| self._restore_backup(backup_name) |
There was a problem hiding this comment.
Hey @Bryan-Roe - I've reviewed your changes - here's some feedback:
- Use the backup_name stored in the deployment_plan to drive the restore in _rollback_deployment rather than relying on a transient self.last_backup attribute, so rollbacks remain consistent even if the automator instance is recreated.
- Wrap the file operations in _restore_backup with try/except and emit errors or abort on failure to avoid partial restores leaving the workspace in an inconsistent state.
- After setting deployment_plan["rollback_backup"], call _save_deployment_record to persist the rollback backup choice in the deployment history.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Use the backup_name stored in the deployment_plan to drive the restore in _rollback_deployment rather than relying on a transient self.last_backup attribute, so rollbacks remain consistent even if the automator instance is recreated.
- Wrap the file operations in _restore_backup with try/except and emit errors or abort on failure to avoid partial restores leaving the workspace in an inconsistent state.
- After setting deployment_plan["rollback_backup"], call _save_deployment_record to persist the rollback backup choice in the deployment history.
## Individual Comments
### Comment 1
<location> `02-ai-workspace/scripts/deployment_automator.py:528` </location>
<code_context>
+ def _restore_backup(self, backup_name: str):
</code_context>
<issue_to_address>
Restoration logic may overwrite files and directories without confirmation.
Unconditionally deleting files or directories risks data loss. Please add a prompt, skip option, or backup for overwritten files, or at minimum log replacements.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def _restore_backup(self, backup_name: str): | ||
| """Restore workspace from a specific backup.""" | ||
| backup_path = self.backup_dir / backup_name | ||
| if not backup_path.exists(): | ||
| print(f"Backup not found: {backup_name}") | ||
| return | ||
|
|
||
| manifest_file = backup_path / "manifest.json" | ||
| if not manifest_file.exists(): | ||
| print("Backup manifest missing, cannot restore") |
There was a problem hiding this comment.
suggestion (bug_risk): Restoration logic may overwrite files and directories without confirmation.
Unconditionally deleting files or directories risks data loss. Please add a prompt, skip option, or backup for overwritten files, or at minimum log replacements.
Summary
DeploymentAutomator_restore_backuphelper to copy backup contents back into the workspace_rollback_deploymentTesting
pytest test_direct_ai.py::test_ai_processing -s(fails: async framework plugin missing)https://chatgpt.com/codex/tasks/task_e_6886a75b21f08322b293e242277abf2c
Summary by Sourcery
Enable automated backup tracking and restore failed deployments by recording backups and applying them during rollback.
New Features: