Drop legacy internal teams (script + tests)#525
Conversation
Adds scripts/drop_legacy_internal_teams.py which: - Identifies internal teams by admin_email pattern (@amazee.io) - Shows activity summary (users, keys, products, last activity) - Soft-deletes them via the existing soft_delete_team() service - Refuses to delete teams with active Stripe products - Supports dry-run (default), --execute, --team-ids, --email-pattern Soft-delete is reversible: sets deleted_at, deactivates users, expires keys in LiteLLM. Hard-delete happens automatically after 60 days via the existing retention job. Closes amazeeio/moad#350
| "\n Remove their product associations first, or exclude them with --team-ids.\n" | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| print(f" 🗑️ Soft-deleting {len(teams)} team(s)...\n") | ||
|
|
||
| succeeded = [] | ||
| failed = [] | ||
|
|
||
| for team in teams: | ||
| try: |
There was a problem hiding this comment.
--email-pattern silently ignored when --team-ids is supplied
Both flags are accepted by the CLI, but when --team-ids is present the email pattern is never applied — the else branch is skipped entirely. A user who passes --email-pattern @someother.io --team-ids 12 34 expecting extra filtering will see all non-deleted teams with those IDs, not just ones matching the pattern. At minimum a warning would help, or the help text could state that --team-ids supersedes --email-pattern.
There was a problem hiding this comment.
Pull request overview
Adds an operator-facing cleanup script to identify and soft-delete legacy “amazee-internal” teams (primarily by admin email pattern), along with tests to validate identification/reporting and safe execution behavior.
Changes:
- Introduces
scripts/drop_legacy_internal_teams.pyto find, summarize, and (optionally) soft-delete legacy internal teams viasoft_delete_team(). - Adds
tests/test_drop_legacy_internal_teams.pyto cover team selection, summary counts, dry-run safety, execute mode behavior, and product-association gating.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| scripts/drop_legacy_internal_teams.py | New runbook-style script to identify/report legacy internal teams and soft-delete them with safety gates. |
| tests/test_drop_legacy_internal_teams.py | New unit tests for the script’s selection logic, summary generation, and delete safety behavior. |
Comments suppressed due to low confidence (1)
tests/test_drop_legacy_internal_teams.py:26
import sys, oson one line violates ruff’s E401 (multiple imports on one line). Split these into separate import statements (and keep imports grouped consistently).
# Import the functions under test — the script lives one level up from tests/
import sys, os
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Safety check: refuse to delete teams with active Stripe products | ||
| teams_with_products = [] | ||
| for team in teams: | ||
| product_count = ( | ||
| db.query(DBTeamProduct).filter(DBTeamProduct.team_id == team.id).count() | ||
| ) | ||
| if product_count > 0: |
| for team in teams: | ||
| try: | ||
| await soft_delete_team(db, team) | ||
| succeeded.append(team) | ||
| print(f" ✅ [{team.id}] {team.name} — soft-deleted") | ||
| except Exception as e: | ||
| failed.append((team, str(e))) | ||
| print(f" ❌ [{team.id}] {team.name} — FAILED: {e}") | ||
|
|
||
| print(f"\n Done. {len(succeeded)} succeeded, {len(failed)} failed.\n") |
| await drop_teams(db, [team], dry_run=False) | ||
|
|
||
| assert exc_info.value.code == 1 | ||
| mock_soft_delete.assert_not_called() |
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
dspachos
left a comment
There was a problem hiding this comment.
- CI is failing
- There are comment by
greptileandcopilotthat should be addressed
- Remove unused datetime import (CodeQL) - Filter product safety check on DBProduct.active only (Copilot) - Exit non-zero when soft-delete partially fails (Copilot) - Warn when --email-pattern is ignored with --team-ids (Greptile) - Split 'import sys, os' into separate lines (lint) - Add test: inactive product associations don't block deletion
…can import The test imports from scripts/drop_legacy_internal_teams.py via sys.path, but the Docker test runner only volume-mounted app/ and tests/. Added scripts/ mount to all three backend-test targets.
|
@dspachos had AI make the changes and seems the tests are now passing |
AI created so we'll need a review
What
Adds
scripts/drop_legacy_internal_teams.py— a runbook-style script to identify and soft-delete legacy amazee-internal teams from the old dashboard.Closes https://github.com/amazeeio/moad/issues/350
Why
Legacy internal teams (Henk's team, Ricardo's team, etc.) create inconsistent state between the old dashboard and Moad. Old dashboard interactions cause the amazee.ai DB to drift from LiteLLM. We're moving to two team types only (Stripe-paying / non-Stripe partner), so this cruft needs to go.
Affected users should sign in to Moad with their amazee email — they'll see the internal workspace and can get keys from there.
How it works
admin_email LIKE %@amazee.io%(configurable via--email-pattern, or target specific IDs with--team-ids)soft_delete_team()service function (setsdeleted_at, deactivates users, expires all keys in LiteLLM)/restoreendpoint; hard-delete happens automatically after 60 days via the retention jobUsage
Tests
tests/test_drop_legacy_internal_teams.pycovers:Checklist (from ticket)
Greptile Summary
Adds a runbook-style script (
scripts/drop_legacy_internal_teams.py) and accompanying tests to identify and soft-delete legacy amazee-internal teams, using the existingsoft_delete_teamservice function with a dry-run default and a product-association safety gate.total_keysfield in the team summary double-counts keys that carry bothteam_idand anowner_idbelonging to the same team, because two additive.count()queries are used instead of a single OR-based query..or_, so the default@amazee.iopattern technically matches any single character in place of the dot.--team-idsand--email-patternare provided, the email pattern is silently discarded.Confidence Score: 4/5
Safe to merge; the script is a human-in-the-loop runbook with a dry-run default, and the actual deletion path is guarded by the product-association check.
The double-counting in total_keys could show an operator a misleadingly high key count for a team, potentially causing them to hesitate on a team that should be deleted. The deletion logic itself is unaffected, but the summary the human reviews before typing --execute may be inaccurate.
scripts/drop_legacy_internal_teams.py — specifically the get_team_summary key-counting logic and the LIKE filter in find_internal_teams.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A([Start]) --> B[Parse args] B --> C{--team-ids provided?} C -- Yes --> D[Filter by explicit IDs] C -- No --> E[Filter by email pattern] D --> F[Exclude already deleted] E --> F F --> G[Build summary per team] G --> H[Print summary table] H --> I{Teams found?} I -- No --> Z([Exit]) I -- Yes --> J{--execute flag?} J -- No --> K[DRY RUN exit] J -- Yes --> L{Any team has products?} L -- Yes --> M[sys.exit 1] L -- No --> N[soft_delete_team loop] N --> O{Exception?} O -- Yes --> P[Log failure / continue] O -- No --> Q[Log success] P --> R{More teams?} Q --> R R -- Yes --> N R -- No --> S[Print done] S --> ZReviews (1): Last reviewed commit: "Add script to drop legacy internal teams" | Re-trigger Greptile