Repository navigation
fix(settings): re-key service configs and overrides stored under the old Azure/GCP service ids - #1551
Conversation
…old Azure/GCP service ids LeanerCloud#1354 made the Settings page save Azure and GCP rows under the service ids recommendations carry, and re-keys older rows only in the browser. Rows already stored under the old per-product ids (azure vm, sql, cosmosdb, redis; gcp sql, memorystore) stayed in service_configs and account_service_overrides, where the scheduler and the purchase modal never match them, until someone re-saves the page. Migration 000151 rewrites them with the same precedence as the frontend's canonicalizeServiceConfigs: a row already stored under the canonical id (per account for overrides) wins and the legacy row is deleted; otherwise the legacy row is re-keyed in place. Each override it deletes or re-keys gets config_audit_events rows (actor 'migration 000151'), so a dropped legacy twin's settings stay recoverable. Both tables are locked IN EXCLUSIVE MODE first, so a legacy row an open transaction commits is still rewritten, and writers that arrive mid-migration read the rewritten row. The down migration is a no-op; its header says what it cannot restore. Closes LeanerCloud#613 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 59 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rG2. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
govulncheck in the Docker image build now fails on these two advisories, both fixed in x/net v0.62.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale: none (the head also carries the Reviewer: rH2. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 1 approving independent review(s) on |
Root cause
Recommendations carry the provider's
common.ServiceTypeslug (compute,relational-db,nosql,cache,search). The scheduler (config.ResolveAccountConfigsForRecs) and the purchase modal look upservice_configsandaccount_service_overridesby that exact(provider, service)pair. Before #1354, the Settings page saved Azure and GCP rows under per-product ids (azure/vm,azure/sql,azure/cosmosdb,azure/redis,gcp/sql,gcp/memorystore). No recommendation carries those ids, so those defaults and overrides never applied.#1354 fixed what the page writes. It re-keys old rows only in the browser: an old service config moves to the new id on the next Save, and an old override only shows a hint. The rows stored in the database stayed under the old ids. Until an admin re-saves Settings, they are still dead. Old overrides stay dead until someone re-creates them.
Fix
Migration
000151_canonical_legacy_settings_service_idsrewrites the stored rows. It uses the mapping fromLEGACY_SETTINGS_SERVICESinfrontend/src/lib/settings-service-ids.ts. There is no backendcanonicalizeServiceConfigs; the only one is the frontend function in that file. The precedence is the same as there:created_atstay the same, and the table's update trigger bumpsupdated_at.gcp/cloudsql, which is a plan id that 000136 already handled and that Settings never wrote, and legacy names under another provider (for exampleaws/vm).The mapping never sends two legacy ids of one provider to the same canonical id, so two re-keys can't collide.
The header has a read-only detection query, as 000136 does. It lists every row the migration will rewrite and whether a twin will win.
Down migration: a no-op, documented in its header.
service_configslegacy row that lost to its twin is gone for good. That table has no audit trail.Audit trail (#1493 / 000147): rows are written
I chose to write
config_audit_eventsrows for every override the migration changes. Deleting a legacy twin destroys user-entered settings, and the trail is the only place they would survive.account_service_override.deleteevent, withbeforeset to the deleted row's settings.deleteevent under the legacy id and anupsert(create) event under the canonical id, both carrying the same settings.migration 000151anddetails{"migration":"000151","reason":…,"legacy_service":…,"canonical_service":…}. They use the existing event types, so no CHECK constraint changes.before/afterhave the same shape asoverrideSnapshotJSON. A test saves the same settings throughSaveAccountServiceOverrideAuditedTxand checks that the twoaftervalues are equal as jsonb.service_configsrewrites record nothing, because feat(audit): record config_audit_events for account service override writes (single and import) #1493's trail covers only overrides.Concurrency
RunMigrationsruns on demand while other instances keep serving requests. The file first runsLOCK TABLE service_configs, account_service_overrides IN EXCLUSIVE MODE. Plain reads still work, but:SELECT … FOR UPDATEreads inUpdateServiceConfigAtomicandSaveAccountServiceOverrideAuditedTx. They then read the re-keyed row, so the merge base and the audit event'sbeforeare the row they actually replace.The migration takes no advisory locks, and no application transaction writes both tables, so there is no lock-order cycle. Both tables are small (one row per service, and per account for overrides).
Closes #613
Verification
Evidence here is local integration tests on real Postgres 16 (testcontainers), on this branch at the PR head. It is not a deployed environment.
What the tests cover:
TestMigration_CanonicalLegacySettingsServiceIDs:service_configs.azure/redisis re-keyed even though account A hasazure/cache.gcp/cloudsql,aws/vm) keepupdated_at.updated_at, audit events), and the helper functions are dropped.TestMigration_CanonicalLegacySettingsServiceIDs_ConcurrentWriters:UpdateServiceConfigAtomicmerges onto the re-keyedgcp/sql→gcp/relational-dbrow.SaveAccountServiceOverrideAuditedTxreplaces the re-keyed override, and its auditbeforeis the re-keyed row's settings.Fail-before and mutation probes (each one reverted after the run):
TestMigration_CanonicalLegacySettingsServiceIDsFAILS withno migration found for version 151.LOCK TABLEline removed:…_ConcurrentWritersFAILS with "the migration must wait for the open service_configs writer".service_configstwinDELETEremoved: the main test FAILS with a unique violation during the migration.Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@400d256b= origin/main7b86e386plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran onf7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1):internal/analytics,internal/api,internal/auth,internal/config,internal/database/postgres/migrations,internal/purchase,internal/schedulerandinternal/serverall pass. Migrations 000149, 000150 and 000151 apply in order on this tree.approval-details,settings-accounts,api) pass (221 tests).accountkey); fixed with named constants. Re-run on this tree: git-secrets, using the repo'sscripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL..tftest.hclfiles were not run locally (CI runs them). Nothing was applied to any cloud.Out of scope
canonicalizeServiceConfigs, the legacy-override note) stays. Removing it is the UI owner's call (see below).PUT /config/service/{provider}/{service}and on override writes. An old browser tab running the pre-fix(frontend): save Azure/GCP settings under recommendation ids #1354 bundle could still write a new legacy row after this migration. The frontend fallback still handles such a row. Rejecting or aliasing legacy ids at the API would be a separate backend change.Frontend follow-up for the UI owner
No UI change is needed for this PR to work. Once 000151 has run everywhere, these become dead code for migrated databases, and the UI owner can remove them:
canonicalizeServiceConfigs.frontend/src/settings.ts.Keep them while old bundles might still write legacy ids (see Out of scope).
Labels
A maintainer, please mirror the issue's triage labels onto this PR:
severity/medium,triaged,priority/p3,urgency/this-quarter,impact/few,effort/s,type/bug.🤖 Generated with Claude Code