Slice D — Config-file migration endpoint POST /v1/admin/config/file/migrate
Part of #1781.
Goal
Provide an explicit, admin-triggered endpoint that copies file-defined config
entities into the platform bucket, so they become API-managed. Migration is on
demand (not on startup), which avoids resurrecting an API-deleted entity on restart.
After migration, the normal rebuild materializes the blob and (per the Slice B+C
blob-shadows-file rule) it becomes authoritative while the file entry still exists;
file entries are retired afterward.
Endpoint
POST /v1/admin/config/file/migrate, under the existing /v1/admin/config/file/*
admin surface (add the route to ControllerSelector; house the handler with
FileConfigController or a sibling admin controller). Admin-only, same auth as the
rest of /v1/admin/*. Runs under LockService.underBucketLocks(ADMIN_BUCKET_LOCATIONS, …)
(it writes to platform), so it is single-writer/multi-pod safe. Triggers
rebuildNow() after a non-dry-run so the new blobs materialize.
Request
Behavior (per requested type, per entity in the file-sourced config)
- Idempotent presence check — skip if the
platform blob already exists,
computed as an in-memory set-diff against the already-loaded canonical ids in the
current Config (no per-entity blob reads). Re-invoking migrates only what is
missing.
- Otherwise write it to
platform/{type}/{shortName} using the same write path the
config-write API uses for that type:
- models / interceptors / roles / routes / keys → the raw-blob +
SecretFieldProcessor
path (reuse AdminApplyController's per-type write helpers, which target
platform).
- applications / toolsets →
ApplicationService / ToolSetService
(external-service / authSettings secret processing, admin-managed fields
AUTHORITATIVE, function/schema prep). A raw SecretFieldProcessor write is
insufficient for these types.
dryRun performs steps 1–2 as a computation only and writes nothing.
Response (synchronous, per-entity result — mirrors AdminApplyController's EntityResult)
Open item — schema migration naming
A file schema is keyed by its $id, but its blob needs a path name for
schemas/platform/{name}. The name source (derive from $id, or a dedicated field)
must be settled before migrating the schemas / catalog_schemas types. Resolve this
as part of this slice, or scope schema migration out of the first cut.
Testing
- Integration — new
ConfigFileMigrateApiTest (full stack, so rebuild + blob +
lock are exercised):
- migrates file entities into
platform; after migration they resolve and list by
short name;
- a second call is a no-op (all
skipped, no writes);
- the
types filter migrates only the requested types;
dryRun writes nothing but reports what would migrate;
- migrated applications/toolsets keep their secrets (encrypted at rest, decrypted in
Config);
- non-admin is rejected.
Depends on
Slice A (apps/toolsets in platform) and Slice B+C (short-name resolution) — the
migrated entities must materialize and resolve by short name.
Slice D — Config-file migration endpoint
POST /v1/admin/config/file/migratePart of #1781.
Goal
Provide an explicit, admin-triggered endpoint that copies file-defined config
entities into the
platformbucket, so they become API-managed. Migration is ondemand (not on startup), which avoids resurrecting an API-deleted entity on restart.
After migration, the normal rebuild materializes the blob and (per the Slice B+C
blob-shadows-file rule) it becomes authoritative while the file entry still exists;
file entries are retired afterward.
Endpoint
POST /v1/admin/config/file/migrate, under the existing/v1/admin/config/file/*admin surface (add the route to
ControllerSelector; house the handler withFileConfigControlleror a sibling admin controller). Admin-only, same auth as therest of
/v1/admin/*. Runs underLockService.underBucketLocks(ADMIN_BUCKET_LOCATIONS, …)(it writes to
platform), so it is single-writer/multi-pod safe. TriggersrebuildNow()after a non-dry-run so the new blobs materialize.Request
Behavior (per requested type, per entity in the file-sourced config)
platformblob already exists,computed as an in-memory set-diff against the already-loaded canonical ids in the
current
Config(no per-entity blob reads). Re-invoking migrates only what ismissing.
platform/{type}/{shortName}using the same write path theconfig-write API uses for that type:
SecretFieldProcessorpath (reuse
AdminApplyController's per-type write helpers, which targetplatform).ApplicationService/ToolSetService(external-service /
authSettingssecret processing, admin-managed fieldsAUTHORITATIVE, function/schema prep). A rawSecretFieldProcessorwrite isinsufficient for these types.
dryRunperforms steps 1–2 as a computation only and writes nothing.Response (synchronous, per-entity result — mirrors
AdminApplyController'sEntityResult)Open item — schema migration naming
A file schema is keyed by its
$id, but its blob needs a path name forschemas/platform/{name}. The name source (derive from$id, or a dedicated field)must be settled before migrating the
schemas/catalog_schemastypes. Resolve thisas part of this slice, or scope schema migration out of the first cut.
Testing
ConfigFileMigrateApiTest(full stack, so rebuild + blob +lock are exercised):
platform; after migration they resolve and list byshort name;
skipped, no writes);typesfilter migrates only the requested types;dryRunwrites nothing but reports what would migrate;Config);Depends on
Slice A (apps/toolsets in
platform) and Slice B+C (short-name resolution) — themigrated entities must materialize and resolve by short name.