diff --git a/CHANGELOG.md b/CHANGELOG.md index e85dae86aee..fcaafbea691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Added initial delay when loading python functions (#8239) - Enforced webframeworks enablement only on webframeworks sites (#8168) - Fixed issue where `apps:init` throws an error upon app creation. +- Reenabled prompts for unused service deletion in `deploy --only`. - Update Firebase Data Connect local toolkit to v1.8.3, which includes the following changes: (#8263) - Adds a `_metadata.distance` field to vector similarity search results - Fixes `auth` and `request.auth` when the request is unauthenticated diff --git a/scripts/dataconnect-test/tests.ts b/scripts/dataconnect-test/tests.ts index 256e40d6888..c50e58feab6 100644 --- a/scripts/dataconnect-test/tests.ts +++ b/scripts/dataconnect-test/tests.ts @@ -32,9 +32,7 @@ function expected( } async function cleanUpService(projectId: string, serviceId: string, databaseId: string) { - await client.deleteServiceAndChildResources( - `projects/${projectId}/locations/us-central1/services/${serviceId}`, - ); + await client.deleteService(`projects/${projectId}/locations/us-central1/services/${serviceId}`); await deleteDatabase(projectId, "dataconnect-test", databaseId); } diff --git a/src/dataconnect/client.spec.ts b/src/dataconnect/client.spec.ts deleted file mode 100644 index 2ebe53b420b..00000000000 --- a/src/dataconnect/client.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { expect } from "chai"; -import * as nock from "nock"; - -import * as client from "./client"; -import { dataconnectOrigin } from "../api"; - -const API_VERSION = "v1"; -describe("DataConnect control plane client", () => { - afterEach(() => { - nock.cleanAll(); - }); - describe("deleteServiceAndChildResources", () => { - it("Should delete all child resources", async () => { - const testService = "projects/test/locations/us-central1/services/test-service"; - const fake = nock(dataconnectOrigin()); - fake - .get(`/${API_VERSION}/${testService}/connectors?pageSize=100&pageToken=&fields=`) - .reply(200, { - connectors: [ - { name: `${testService}/connectors/c1` }, - { name: `${testService}/connectors/c2` }, - ], - }); - fake - .delete(`/${API_VERSION}/${testService}/connectors/c1`) - .reply(200, { name: "projects/test/operations/abc123" }); - fake - .delete(`/${API_VERSION}/${testService}/connectors/c2`) - .reply(200, { name: "projects/test/operations/def456" }); - fake - .delete(`/${API_VERSION}/${testService}/schemas/main`) - .reply(200, { name: "projects/test/operations/ghi123" }); - fake - .delete(`/${API_VERSION}/${testService}`) - .reply(200, { name: "projects/test/operations/jkl456" }); - fake.get(`/${API_VERSION}/projects/test/operations/abc123`).reply(200, { done: true }); - fake.get(`/${API_VERSION}/projects/test/operations/def456`).reply(200, { done: true }); - fake.get(`/${API_VERSION}/projects/test/operations/ghi123`).reply(200, { done: true }); - fake.get(`/${API_VERSION}/projects/test/operations/jkl456`).reply(200, { done: true }); - - await client.deleteServiceAndChildResources(testService); - - expect(nock.isDone()).to.be.true; - }); - - it("Succeed when there are no connectors", async () => { - const testService = "projects/test/locations/us-central1/services/test-service"; - const fake = nock(dataconnectOrigin()); - fake - .get(`/${API_VERSION}/${testService}/connectors?pageSize=100&pageToken=&fields=`) - .reply(200, { - connectors: [], - }); - fake - .delete(`/${API_VERSION}/${testService}/schemas/main`) - .reply(200, { name: "projects/test/operations/ghi123" }); - fake - .delete(`/${API_VERSION}/${testService}`) - .reply(200, { name: "projects/test/operations/jkl456" }); - fake.get(`/${API_VERSION}/projects/test/operations/ghi123`).reply(200, { done: true }); - fake.get(`/${API_VERSION}/projects/test/operations/jkl456`).reply(200, { done: true }); - - await client.deleteServiceAndChildResources(testService); - - expect(nock.isDone()).to.be.true; - }); - - it("Succeed when there is no schema", async () => { - const testService = "projects/test/locations/us-central1/services/test-service"; - const fake = nock(dataconnectOrigin()); - fake - .get(`/${API_VERSION}/${testService}/connectors?pageSize=100&pageToken=&fields=`) - .reply(200, { - connectors: [], - }); - fake.delete(`/${API_VERSION}/${testService}/schemas/main`).reply(404, {}); - fake - .delete(`/${API_VERSION}/${testService}`) - .reply(200, { name: "projects/test/operations/jkl456" }); - fake.get(`/${API_VERSION}/projects/test/operations/jkl456`).reply(200, { done: true }); - - await client.deleteServiceAndChildResources(testService); - - expect(nock.isDone()).to.be.true; - }); - }); -}); diff --git a/src/dataconnect/client.ts b/src/dataconnect/client.ts index 1d21bcd1720..68aff8da640 100644 --- a/src/dataconnect/client.ts +++ b/src/dataconnect/client.ts @@ -61,9 +61,11 @@ export async function createService( return pollRes; } -async function deleteService(serviceName: string): Promise { - // NOTE(fredzqm): Don't force delete yet. Backend would leave orphaned resources. - const op = await dataconnectClient().delete(serviceName); +export async function deleteService(serviceName: string): Promise { + // Note that we need to force delete in order to delete child resources too. + const op = await dataconnectClient().delete(serviceName, { + queryParams: { force: "true" }, + }); const pollRes = await operationPoller.pollOperation({ apiOrigin: dataconnectOrigin(), apiVersion: DATACONNECT_API_VERSION, @@ -72,19 +74,6 @@ async function deleteService(serviceName: string): Promise { return pollRes; } -export async function deleteServiceAndChildResources(serviceName: string): Promise { - const connectors = await listConnectors(serviceName); - await Promise.all(connectors.map(async (c) => deleteConnector(c.name))); - try { - await deleteSchema(serviceName); - } catch (err: any) { - if (err.status !== 404) { - throw err; - } - } - await deleteService(serviceName); -} - /** Schema methods */ export async function getSchema(serviceName: string): Promise { diff --git a/src/dataconnect/schemaMigration.ts b/src/dataconnect/schemaMigration.ts index c6c05fb76fe..c1d6f133dda 100644 --- a/src/dataconnect/schemaMigration.ts +++ b/src/dataconnect/schemaMigration.ts @@ -487,7 +487,7 @@ async function promptForInvalidConnectorError( !options.nonInteractive && (await confirm({ ...options, - message: `Would you like to delete and recreate these connectors? This will cause ${clc.red(`downtime.`)}.`, + message: `Would you like to delete and recreate these connectors? This will cause ${clc.red(`downtime`)}.`, })) ) { return true; diff --git a/src/deploy/dataconnect/deploy.ts b/src/deploy/dataconnect/deploy.ts index 74d5b72b31e..76936ac52a6 100644 --- a/src/deploy/dataconnect/deploy.ts +++ b/src/deploy/dataconnect/deploy.ts @@ -9,6 +9,7 @@ import { ResourceFilter } from "../../dataconnect/filters"; import { vertexAIOrigin } from "../../api"; import * as ensureApiEnabled from "../../ensureApiEnabled"; import { join } from "node:path"; +import { confirm } from "../../prompt"; /** * Checks for and creates a Firebase DataConnect service, if needed. @@ -56,28 +57,22 @@ export default async function ( ); if (servicesToDelete.length) { - const warning = `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${servicesToDelete - .map((s) => s.name) - .join("\n")}\nConsider deleting these via the Firebase console if they are no longer needed.`; - utils.logLabeledWarning("dataconnect", warning); - // TODO: Switch this back to prompting for deletion. - // if ( - // await confirm({ - // force: options.force, - // nonInteractive: options.nonInteractive, - // message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${servicesToDelete - // .map((s) => s.name) - // .join("\n")}\nWould you like to delete these services?`, - // }) - // ) { - // await Promise.all( - // servicesToDelete.map(async (s) => { - // const { projectId, locationId, serviceId } = splitName(s.name); - // await client.deleteService(projectId, locationId, serviceId); - // utils.logLabeledSuccess("dataconnect", `Deleted service ${s.name}`); - // }), - // ); - // } + if ( + await confirm({ + force: options.force, + nonInteractive: options.nonInteractive, + message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${servicesToDelete + .map((s) => s.name) + .join("\n")}\nWould you like to delete these services?`, + }) + ) { + await Promise.all( + servicesToDelete.map(async (s) => { + await client.deleteService(s.name); + utils.logLabeledSuccess("dataconnect", `Deleted service ${s.name}`); + }), + ); + } } // Provision CloudSQL resources