fix(diff): VPC/Droplet/AppPlatform Diff now compares region (#70)#71
Merged
fix(diff): VPC/Droplet/AppPlatform Diff now compares region (#70)#71
Conversation
Previously only VolumeDriver.Diff compared `region` against current.Outputs["region"]; the other three regional-resource drivers (VPCDriver, DropletDriver, AppPlatformDriver) silently no-op'd on region changes despite DO having no in-place region update API for those resources. ## Bug surfaced by Core-dump TC2 staging cutover (https://github.com/GoCodeAlone/core-dump/actions/runs/25383166075): plan against an infra.yaml whose VPC/Volume/Droplet/App regions moved nyc3 → nyc1. Pre-cutover DO state confirmed all 5 resources in nyc3. Plan output: only Volume got a `replace` action; VPC and Droplet were absent from the plan entirely; App got `update` (should be replace). The plan-shape assertion in core-dump's tc2-cutover.yml caught the discrepancy before any cloud mutation. Without the assertion gate, apply would have updated the firewall + App in nyc1 and replaced the Volume to nyc1, while leaving VPC + Droplet in nyc3 — producing a half-migrated state requiring manual cleanup (App's vpc_ref pointing at nyc3 VPC, Volume in nyc1 unable to mount on a nyc3 Droplet, etc.). ## Fix Add the canonical region-comparison block (mirroring VolumeDriver.Diff line 165-173) to the three affected drivers: - internal/drivers/vpc.go::VPCDriver.Diff - internal/drivers/droplet.go::DropletDriver.Diff - internal/drivers/app_platform.go::AppPlatformDriver.Diff Each emits `FieldChange{Path: "region", ForceNew: true}` and sets `NeedsReplace=true` when desired.region differs from current.region. ## Upgrade-safe guard The pattern guards on `curRegion != ""` so resources in state from earlier plugin versions (when their respective output functions didn't include region) do not false-positive on the first plan after upgrade. They will Read on next apply to populate the field naturally. dropletOutput + vpcOutput already populate `region`. appOutput did NOT — this PR also adds `region: app.Spec.Region` to the appOutput map so the Diff comparison has a value to compare against. ## Tests Added regression tests for each driver: - TestVPCDriver_Diff_RegionChangeForcesReplace + RegionEmptyCurrentSkipped - TestDropletDriver_Diff_RegionChangeForcesReplace + RegionEmptyCurrentSkipped - TestAppPlatformDriver_Diff_RegionChangeForcesReplace + RegionEmptyCurrentSkipped Each pair covers (a) the change-detection path (current=nyc3, desired=nyc1, asserts ForceNew + NeedsReplace) and (b) the upgrade-safe skip path (current.Outputs missing "region", asserts no false-positive drift). ## Validation - `go build ./...` (GOWORK=off): clean - `go test -count=1 ./internal/drivers/`: all 6.1s pass - `go test -count=1 ./...`: all packages pass ## Scope Only the 3 Diff fixes + their regression tests + appOutput addition + CHANGELOG entry. No other Diff changes; no public API changes; no .github/workflows touch. Per upstream issue #70 scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a planning blind spot in the DigitalOcean IaC provider by ensuring Diff detects region drift for VPCs, Droplets, and App Platform apps, and marks those changes as replace-only (ForceNew). This prevents incomplete “region migration” plans that would otherwise leave some resources in the old region while dependents move.
Changes:
- Add
regioncomparisons toVPCDriver.Diff,DropletDriver.Diff, andAppPlatformDriver.Diff, emittingFieldChange{ForceNew: true}on drift. - Extend
appOutputto includeOutputs["region"]so App Platform diffs have a current-side value to compare. - Add regression tests for “region change forces replace” and “missing current region is skipped” cases, plus a CHANGELOG entry.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
internal/drivers/vpc.go |
Adds region drift detection in VPCDriver.Diff and marks drift as ForceNew. |
internal/drivers/vpc_test.go |
Adds tests covering replace-on-region-drift and upgrade-safe empty-current behavior for VPCs. |
internal/drivers/droplet.go |
Adds region drift detection in DropletDriver.Diff and marks drift as ForceNew. |
internal/drivers/droplet_test.go |
Adds tests covering replace-on-region-drift and upgrade-safe empty-current behavior for Droplets. |
internal/drivers/app_platform.go |
Adds region drift detection to Diff (replace-only) and includes region in appOutput. |
internal/drivers/app_platform_test.go |
Adds tests covering replace-on-region-drift and upgrade-safe empty-current behavior for App Platform. |
CHANGELOG.md |
Documents the fix under [Unreleased]. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…view) Copilot caught a contradiction in the CHANGELOG entry: the original wording said "appOutput, vpcOutput, dropletOutput already populate region" but this PR is also ADDING region to appOutput.Outputs. Reworded to: vpcOutput + dropletOutput already had it; appOutput gets it in this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Closes #70. Adds region comparison to VPCDriver, DropletDriver, AppPlatformDriver Diff functions.
Surfaced by core-dump TC2 staging cutover (run) where the cascade-replace plan-shape assertion expected 5 replace actions but got 1 (Volume only). Without the gate, apply would have left VPC + Droplet in nyc3 while moving App + Volume to nyc1 — half-migrated state requiring manual cleanup.
Changes
internal/drivers/vpc.gointernal/drivers/droplet.gointernal/drivers/app_platform.goregionto appOutput so Diff has a current-side value to compareinternal/drivers/vpc_test.gointernal/drivers/droplet_test.gointernal/drivers/app_platform_test.goCHANGELOG.mdTotal: 6 source files + CHANGELOG, 266 insertions.
Pattern
All three drivers now follow VolumeDriver.Diff's canonical region block (lines 165-173):
The
curRegion != ""guard is the upgrade-safe path: resources in state from earlier plugin versions (when their output functions didn't include region) don't false-positive on the first plan after upgrade — they'll Read on next apply to populate the field naturally.Validation
Scope discipline
Only the 3 Diff fixes + their regression tests + the appOutput.region addition + CHANGELOG entry. No other Diff changes; no public API changes; no
.github/workflowstouch.Test plan
Downstream
After v0.10.2 ships, core-dump's
.wfctl-lock.yaml+wfctl.yamlbump v0.10.1 → v0.10.2 in a follow-up PR. Re-dispatchingtc2-cutover.ymlwill then emit the expected 5-resource cascade.🤖 Generated with Claude Code