feat: add ringctl patch revert command structure
What does this MR do?
Implements ringctl patch revert
command to automate patch reversion during incident response.
Implementation
Command: ringctl patch revert <patch-id> -e <environment> [--dry-run] [--priority N]
Complete revert flow:
- Validates patch contains only supported operations (replace) more operations to be implemented later
- Finds the commit that applied the patch using GitLab Search API
- Extracts old attribute values by reading cell files at parent commit
- Creates new revert patch with extracted old values
- Deletes the failed patch
- Both operations in a single MR with descriptive context
Components added:
-
cmd/patch_revert.go
- Command implementation with operation validation -
lib/tissue/client.go
- AddedFindPatchApplicationCommit()
,Instance()
,Project()
,Git()
-
lib/gitlab/client_wrapper.go
- AddedSearchCommits()
andCompare()
wrappers -
lib/patch/revert.go
- AddedExtractOldValues()
,extractValuesFromCellFile()
,CreateRevertOperations()
-
lib/tissue/templates/patch_mr.md
- Enhanced with revert-specific description -
lib/tissue/gitlab_cells_storage.go
- Improved URL parsing with regex - Tests:
lib/patch/revert_test.go
,lib/tissue/client_test.go
- 9 test cases total
Key Design Decisions
Robust value extraction:
- Uses GitLab Search API (single call vs pagination)
- Reads actual cell files at parent commit
- Parses JSON directly (not regex on diff text)
- Handles complex JSON values correctly
Revert patch behavior:
- Starts from failed ring (editable in generated MR)
- Sets
completed_after_ring
to failed ring - Priority default: 1 (executes before other patches)
- Origin: "revert" for tracking
- Creates new unique patch ID
Error handling:
- Validates only replace operations supported
- Cleanup revert patch if delete fails
- Comprehensive error messages with context
Testing
Unit tests (9 test cases):
go test ./lib/patch ./lib/tissue -v
# All tests pass ✓
Coverage:
-
CreateRevertOperations
- Operation creation logic -
extractValuesFromCellFile
- JSON parsing and extraction -
ExtractOldValues
- Complete extraction flow with mocked API -
FindPatchApplicationCommit
- Commit search with mocked Search API
Verified with cellsdev:
ringctl patch revert 01K61PZHDYKD6WBKX7NHQPKH9V -e cellsdev --priority 2
Results:
- Created MR: https://ops.gitlab.net/gitlab-com/gl-infra/cells/tissue/-/merge_requests/1151
- Revert patch:
01K7P3555EZRZQQJYMN8GF9HB0
(priority 2, starts failed ring) - Deleted patch:
01K61PZHDYKD6WBKX7NHQPKH9V
- MR description: "reverts patch X by deleting it and creating..."
Dry-run support:
ringctl patch revert 01K61PZHDYKD6WBKX7NHQPKH9V -e cellsdev --dry-run
Shows complete revert preview without making changes.
Reviewer Feedback Addressed
Robustness improvements:
-
✅ JSON parsing instead of regex for old value extraction -
✅ GitLab Search API instead of commit pagination -
✅ Proper parent commit handling via ParentIDs -
✅ Transaction cleanup if delete fails
Code quality:
-
✅ Reduced cognitive complexity by extracting helper functions -
✅ Configurable constants (maxPages, commit message format) -
✅ Robust URL parsing with regex pattern -
✅ Comprehensive error handling
Metadata correctness:
-
✅ Starts from failed ring (reviewers can adjust in MR if needed) -
✅ Setscompleted_after_ring
to failed ring -
✅ Dynamic URL construction using client methods -
✅ Enhanced MR template for revert context
Current Scope
Supported operations: Replace operations only (covers 90%+ of patches)
Validation: Fails fast with clear error for unsupported operation types
Future enhancements (separate MRs):
- Support add/remove/move/copy operations
- Additional edge case handling
Why
Automates the manual reversion process documented in the patching runbook:
Manual deletion viaringctl patch delete
Manual git history inspection for old valuesManual revert patch creationManual priority calculation
Related: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/21502
Edited by Maina Ng'ang'a