-
Notifications
You must be signed in to change notification settings - Fork 5
Add Python CICD fixes and rollback handling to server.py #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add a CI/CD check function to server.py
Add git rollback if docker-compose fails with python in server.py
Updated rollback handling to log messages without executing git reset in development in the terminal
Removed mock rollback message for local development.
Added rollback functionality with backup branch creation and deletion.
Removed unused import for datetime module.
server.py
Outdated
| name: str | ||
| branch: str | ||
| path: str | ||
| force_recreate: bool = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we remove this for now
server.py
Outdated
| if 'enable_rollback' not in config: | ||
| config['enable_rollback'] = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have to do this, would enable_rollback just default to false if we dont pass anything in to the dataclass
server.py
Outdated
| backup_branch_name = None | ||
| # Backup branch logic | ||
| if getattr(repo_config, 'enable_rollback', False): | ||
| from datetime import datetime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets import this at the top of the file, and import it as import datetime
i know we have to then do datetime.datetime.whatever, but it makes it easier to follow as we are using the base module, and write out the full paths to each function
server.py
Outdated
| backup_branch_name = datetime.now().strftime('%Y%m%d-%H%M%S') + f'-{repo_config.branch}' | ||
| try: | ||
| # double check if on the main branch | ||
| subprocess.run(["git", "checkout", repo_config.branch], cwd=repo_config.path, check=True) | ||
| # create backup branch | ||
| subprocess.run(["git", "checkout", "-b", backup_branch_name], cwd=repo_config.path, check=True) | ||
| # switch back to main | ||
| subprocess.run(["git", "checkout", repo_config.branch], cwd=repo_config.path, check=True) | ||
| logger.info(f"Created backup branch {backup_branch_name} for rollback protection.") | ||
| except Exception as e: | ||
| logger.error(f"Failed to create backup branch {backup_branch_name}: {e}") | ||
| result.rollback_message = f"Failed to create backup branch: {e}" | ||
| result.backup_branch_name = backup_branch_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this be its own function, like create_copy_of_cicd_branch and we pass in repo_config as a param
the function could then return the name of the branch we created as the copy
server.py
Outdated
| if docker_result.returncode != 0: | ||
| logger.warning("\nROLLBACK INITIATED\n") | ||
| logger.warning(f"docker-compose failed (exit code {docker_result.returncode}), git pull exit code {git_result.returncode}.") | ||
| rollback_success = False | ||
| rollback_error = None | ||
| if getattr(repo_config, 'enable_rollback', False) and hasattr(result, 'backup_branch_name') and result.backup_branch_name: | ||
| try: | ||
| #reset main to backup branch | ||
| subprocess.run(["git", "checkout", repo_config.branch], cwd=repo_config.path, check=True) | ||
| subprocess.run(["git", "reset", "--hard", result.backup_branch_name], cwd=repo_config.path, check=True) | ||
| #delete backup branch | ||
| subprocess.run(["git", "branch", "-D", result.backup_branch_name], cwd=repo_config.path, check=True) | ||
| logger.info(f"Rolled back {repo_config.branch} to {result.backup_branch_name} and deleted backup branch.") | ||
| rollback_success = True | ||
| except Exception as e: | ||
| logger.error(f"Rollback failed: {e}") | ||
| rollback_error = str(e) | ||
| else: | ||
| logger.warning("Rollback skipped: no backup branch available.") | ||
| logger.warning("\nROLLBACK COMPLETE\n") | ||
| result.git_exit_code = 1 | ||
| result.docker_exit_code = 1 | ||
| if rollback_success: | ||
| result.rollback_message = f"Deployment failed, rollback performed using backup branch {getattr(result, 'backup_branch_name', '')}." | ||
| # Retry docker-compose up after rollback | ||
| try: | ||
| retry_result = subprocess.run( | ||
| ["docker-compose", "up", "--build", "-d"], | ||
| cwd=repo_config.path, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| logger.info(f"Docker compose retry stdout: {retry_result.stdout}") | ||
| logger.info(f"Docker compose retry stderr: {retry_result.stderr}") | ||
| result.docker_stdout += f"\n[rollback retry]\n{retry_result.stdout}" | ||
| result.docker_stderr += f"\n[rollback retry]\n{retry_result.stderr}" | ||
| result.docker_exit_code = retry_result.returncode | ||
| except Exception as e: | ||
| logger.error(f"Retry after rollback failed: {e}") | ||
| else: | ||
| result.rollback_message = f"Deployment failed, rollback logic triggered but failed: {rollback_error or 'No backup branch.'}" | ||
| push_update_success_as_discord_embed(repo_config, result) | ||
| return result | ||
| if getattr(repo_config, 'enable_rollback', False) and hasattr(result, 'backup_branch_name') and result.backup_branch_name: | ||
| try: | ||
| subprocess.run(["git", "branch", "-D", result.backup_branch_name], cwd=repo_config.path, check=True) | ||
| logger.info(f"Deleted backup branch {result.backup_branch_name} after successful deployment.") | ||
| except Exception as e: | ||
| logger.error(f"Failed to delete backup branch {result.backup_branch_name}: {e}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets make this its own function
and we can call it like
if docker_result.returncode != 0 and repo_config.enable_rollback:
do_rollback(repo_config, copy_branch_name)Added import datetime to the top, removed force_recreate and enable_rollback, made own functions for create_copy_of_cicd_branch and do rollback
evanugarte
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets add one more field, bool | None called maybe_rollback_result
it is default None meaning it didnt happen. but if we did a rollback, we set it to true or false, the return value of do_rollback
we should also have a section in the embed to check for this field being non-none and have some text saying a rollback happened and it worked/didnt work
server.py
Outdated
| except Exception as e: | ||
| logger.error(f"Failed to create backup branch {copy_branch_name}: {e}") | ||
| return None | ||
| def do_rollback(repo_config, copy_branch_name, result): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could do_rollback instead just return True or False if it was able to rollback
we can skip passing the result param in
Co-authored-by: Evan Ugarte <[email protected]>
Co-authored-by: Evan Ugarte <[email protected]>
Added maybe_rollback_result attribute to track rollback status.
Refactor rollback logic to simplify return statements and remove unnecessary error handling.
|
Co-authored-by: Evan Ugarte <[email protected]>
Add a CI/CD check function to server.py
Steps on how to test:
My example:
repos:
branch: main
path: /Users/Brian/sce-cicd
force_recreate: false
Triggered a webhook event using a curl command
Ex:
curl command to test deployment successful:
curl -X POST
-H "Content-Type: application/json"
-H "X-GitHub-Event: push"
--data '{
"ref": "refs/heads/main",
"repository": {"name": "sce-cicd"},
"head_commit": {
"id": "abc123def456",
"message": "Deployment successful test commit",
"branch": "main",
"url": "abc123def456",
"author": {"name": "Test User", "username": "testuser", "url": "https://github.com/testuser"}
}
}'
http://127.0.0.1:3000/webhook
curl command to test rollback:
curl -X POST
-H "Content-Type: application/json"
-H "X-GitHub-Event: push"
--data '{
"ref": "refs/heads/main",
"repository": {"name": "sce-cicd"},
"head_commit": {
"id": "abc123def456",
"message": "Test commit",
"branch": "main",
"url": "abc123def456",
"author": {"name": "Test User", "username": "testuser", "url": "https://github.com/testuser"}
}
}'
http://127.0.0.1:3000/webhook
Terminal Output:
You should see logs indicating that a push was detected. If docker-compose fails a rollback log message will appear in the terminal like this:
" ROLLBACK INITIATED
docker-compose failed (exit code ...), git pull exit code .... Rollback would be performed here (mocked in dev).
Rollback skipped for local development. No destructive git reset performed.
ROLLBACK COMPLETE"
If your discord webhook is set up, you will see a deployment status embed in your Discord channel. To create a webhook to test the embed, follow this tutorial: https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
Output:


The first screenshot shows when a commit is sucessful. The second screenshot shows when a commit is failed and invalid, which proceeds to run the rollback command: