Fix #37: release DO firewalls on team destroy + sweep command for historical orphans#38
Merged
Merged
Conversation
ProvisionDigitalOceanServerJob creates a per-droplet firewall but none of our destroy paths released it, leaking one firewall per provisioned team. Confirmed 36 orphans accumulated in the dev DigitalOcean account. Changes: - New migration: servers.provider_firewall_id (nullable string). - DigitalOceanService::deleteFirewall — 404 = already gone. - ProvisionDigitalOceanServerJob persists firewall ID on the server row after createFirewall. - DestroyTeamJob::destroyDigitalOcean calls deleteFirewall after the droplet/volume cleanup; failures are logged but non-fatal. - New artisan command `cloud:cleanup-orphan-firewalls --dry-run` to sweep historical orphans. Filters by name prefix (provision-, warm-) so it can't accidentally delete unrelated firewalls. Tests: - Provision job test: provider_firewall_id persisted from createFirewall response. - DestroyTeamJob test: deleteFirewall called when server has firewall ID. - Existing DO destroy test still passes (server with null firewall ID takes the no-op path). Out of scope: Linode has the same bug pattern (ProvisionLinodeServerJob:72 createFirewall, destroyLinode doesn't release). File separately if Linode is in active use. Fixes #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ProvisionDigitalOceanServerJobcreates a per-droplet firewall (createFirewall(\"provision-{server.id}\", ...)) but none of our destroy paths released it. Every destroyed team left a firewall behind in DO. The dev account had 36 orphans when this was discovered — droplets and volumes were both 0.Firewalls don't bill, but DO enforces a per-account cap (default 100) and the Networking → Firewalls UI becomes useless past a few dozen entries.
Fixes #37.
Changes
2026_05_22_173614_add_provider_firewall_id_to_servers_table.php: adds nullableservers.provider_firewall_id.DigitalOceanService::deleteFirewall(new):DELETE /v2/firewalls/{id}, treats 404 as already-gone.ProvisionDigitalOceanServerJob.php:66-72: persists the firewall ID returned bycreateFirewallonto the server row.DestroyTeamJob::destroyDigitalOcean: callsdeleteFirewallafter the droplet+volume cleanup; failure is logged but non-fatal (the cleanup command can sweep survivors).Servermodel:provider_firewall_idadded to$fillable.cloud:cleanup-orphan-firewallscommand (new): one-shot sweep for historical orphans. Filters by--prefix=provision-,warm-so it cannot accidentally delete unrelated firewalls. Supports--dry-run.Tests
ProvisionDigitalOceanServerJobTest: new — persists the firewall ID on the server so destroy can release it. Assertsprovider_firewall_idis populated from the mockedcreateFirewallresponse.DestroyTeamTest: new — releases the DO firewall when destroying a team that has one. AssertsdeleteFirewall('fw-leak-37')is called.Full suite:
Pint: clean.
Live verification (already done)
The dev account had 36 orphans (
provision-*× 34,warm-*× 2). All were deleted via direct API calls before this PR was opened. DO is now at:After this PR ships, no new orphans should accrue from the destroy path. For other accounts (production, other developers) with historical orphans, run:
Out of scope
ProvisionLinodeServerJob.php:72has the same bug pattern —createFirewallis called, butdestroyLinodeonly releases the droplet and volume. Worth a follow-up issue if Linode is in active use; tagging here for visibility but not fixing in this PR (different API, different mock structure, would muddy the diff).Test plan
curl … /v2/firewalls | jq '.firewalls | length'→ 0)php artisan cloud:cleanup-orphan-firewalls --dry-runreports 0 in a clean account