diff --git a/kits/storage-resize-images/CHANGELOG.md b/kits/storage-resize-images/CHANGELOG.md index 1a874569e..c1679ad95 100644 --- a/kits/storage-resize-images/CHANGELOG.md +++ b/kits/storage-resize-images/CHANGELOG.md @@ -1,3 +1,5 @@ +- fix: remove `europe-north2` and `northamerica-south1` from the `BUCKET_REGION` options. Cloud Functions has no location in either, so choosing one failed the deploy with `Location europe-north2 is not found or access is unauthorized.` The Firebase CLI only lets a function listen to a regional bucket from the same region, so the kit cannot serve a bucket in either region. Setting either in `.env` now throws an error saying so when the functions load. + - fix: `generateResizedImage` now handles one event per instance, as the extension's 1st gen trigger did, and sizes its CPU from `FUNCTION_MEMORY` the 1st gen way. Left unset, the Firebase CLI deployed it with 1 vCPU and a concurrency of 80, so a burst of uploads landed on a single instance and every resize shared its CPU. With 80 large images uploaded at once, 58 requests reached the 60s timeout and 56 images were never resized: the trigger has no retry policy, the timeouts were logged as HTTP 200 at INFO severity, and no `onError` event or failed-image copy was written. The next deploy updates the function in place; no `.env` change is needed. - fix: an Eventarc publish failure no longer fails the function. A channel that was deleted or is unreachable answers `PERMISSION_DENIED`, and the publish was awaited before the work the function exists to do, so the image was never resized. The publish failure is now logged as a warning and the invocation continues. Deploys also declare `eventarcpublishing.googleapis.com`, so the API is enabled on your project whether or not you use events. diff --git a/kits/storage-resize-images/README.md b/kits/storage-resize-images/README.md index 1eb671c42..12cd4fdbe 100644 --- a/kits/storage-resize-images/README.md +++ b/kits/storage-resize-images/README.md @@ -234,6 +234,10 @@ inside them - `us` to `us-east1`, `eu` to `europe-west1`, `asia` to `asia-east1` - because they are not Cloud Run regions themselves and would fail the deploy. The value is matched case-insensitively. +Cloud Functions has no location in `europe-north2` or `northamerica-south1`, so +the kit can't serve a bucket there. The extension can: keep using it, or move to +a listed region. + Dual-region buckets (`nam4`, `eur4`, `asia1`) are not offered as such and are not mapped. Pick one of the regions the pair is made of instead, all of which are in the list: `us-central1` or `us-east1` for `nam4`, `europe-north1` or diff --git a/kits/storage-resize-images/src/config.ts b/kits/storage-resize-images/src/config.ts index e33951939..b276f4e63 100644 --- a/kits/storage-resize-images/src/config.ts +++ b/kits/storage-resize-images/src/config.ts @@ -68,7 +68,6 @@ const params = { "Dallas (us-south1)": "us-south1", "Montreal (northamerica-northeast1)": "northamerica-northeast1", "Toronto (northamerica-northeast2)": "northamerica-northeast2", - "Queretaro (northamerica-south1)": "northamerica-south1", "Sao Paulo (southamerica-east1)": "southamerica-east1", "Santiago (southamerica-west1)": "southamerica-west1", "Belgium (europe-west1)": "europe-west1", @@ -82,7 +81,6 @@ const params = { "Turin (europe-west12)": "europe-west12", "Madrid (europe-southwest1)": "europe-southwest1", "Finland (europe-north1)": "europe-north1", - "Stockholm (europe-north2)": "europe-north2", "Warsaw (europe-central2)": "europe-central2", "Doha (me-central1)": "me-central1", "Dammam (me-central2)": "me-central2", diff --git a/kits/storage-resize-images/src/region.ts b/kits/storage-resize-images/src/region.ts index 08d30c92a..98d273676 100644 --- a/kits/storage-resize-images/src/region.ts +++ b/kits/storage-resize-images/src/region.ts @@ -25,12 +25,18 @@ const MULTI_REGION_TO_FUNCTION_REGION: Record = { asia: "asia-east1", }; +// Cloud Functions has no location in these, and a 2nd gen storage trigger +// cannot cross regions. The CLI does not check `.env` values against the +// BUCKET_REGION options, so a hand-written one must be rejected here. +const UNSUPPORTED_LOCATIONS = ["europe-north2", "northamerica-south1"]; + /** * Maps a Cloud Storage bucket location to the Cloud Run region the functions * should deploy to. The lookup is case-insensitive and ignores surrounding * whitespace, as the CLI's own region handling is. Regional locations pass * through lowercased; an unset or blank location returns `undefined`, meaning - * the functions declare no region. + * the functions declare no region. Throws for a location Cloud Functions + * cannot serve. * * Dual-region locations (`nam4`, `eur4`, `asia1`, ...) are not mapped and pass * through, which fails the deploy. firebase-tools has the same gap, so a @@ -45,5 +51,10 @@ export function bucketLocationToFunctionRegion( if (!normalized) { return undefined; } + if (UNSUPPORTED_LOCATIONS.includes(normalized)) { + throw new Error( + `BUCKET_REGION ${normalized} is not supported: Cloud Functions has no location there, so the kit cannot serve a bucket in it.` + ); + } return MULTI_REGION_TO_FUNCTION_REGION[normalized] ?? normalized; } diff --git a/kits/storage-resize-images/tests/function-region.test.ts b/kits/storage-resize-images/tests/function-region.test.ts index d95ef8e54..61f860e5c 100644 --- a/kits/storage-resize-images/tests/function-region.test.ts +++ b/kits/storage-resize-images/tests/function-region.test.ts @@ -15,6 +15,7 @@ */ import { afterEach, describe, expect, test } from "vitest"; +import { declaredParams } from "firebase-functions/params"; import { envFunctionRegion } from "../src/config"; @@ -60,3 +61,23 @@ describe("envFunctionRegion function region", () => { expect(envFunctionRegion()).toBeUndefined(); }); }); + +describe("BUCKET_REGION options", () => { + test.each(["europe-north2", "northamerica-south1"])( + "do not offer %s, which has no Cloud Functions location", + (region) => { + const declaration = ( + declaredParams as unknown as { + name: string; + options?: { input?: { select?: { options: { value: string }[] } } }; + }[] + ).find((param) => param.name === "BUCKET_REGION"); + const values = declaration?.options?.input?.select?.options.map( + (option) => option.value + ); + + expect(values).toContain("europe-north1"); + expect(values).not.toContain(region); + } + ); +}); diff --git a/kits/storage-resize-images/tests/region.test.ts b/kits/storage-resize-images/tests/region.test.ts index df2b16ee4..57fad418b 100644 --- a/kits/storage-resize-images/tests/region.test.ts +++ b/kits/storage-resize-images/tests/region.test.ts @@ -54,6 +54,15 @@ describe("bucketLocationToFunctionRegion", () => { expect(bucketLocationToFunctionRegion(location)).toBe(region); }); + test.each(["europe-north2", "northamerica-south1", " Europe-North2 "])( + "rejects %s, which has no Cloud Functions location", + (location) => { + expect(() => bucketLocationToFunctionRegion(location)).toThrow( + /Cloud Functions has no location there/ + ); + } + ); + test("lowercases and trims a regional location", () => { expect(bucketLocationToFunctionRegion(" Europe-West2 ")).toBe( "europe-west2"