t3-code-android-nightly/.repos/alchemy-effect/examples/gcp-service-to-service
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00
..
src chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
test chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
alchemy.run.ts chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
package.json chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
README.md chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
tsconfig.json chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00

gcp-service-to-service

One Cloud Run service calling another, private one with Google-signed identity. This is the GCP counterpart of AWS.Lambda.InvokeFunction.

Service Access Routes
Gateway public GET / (health), GET /quote?n= (proxy)
Quotes private GET /quote?n=
  • Quotes (src/Quotes.ts) keeps Cloud Run's invoker IAM check on (the default). Requests without a valid ID token get 403 from Cloud Run's front end and never reach the container.
  • Gateway (src/Gateway.ts) sets invokerIamDisabled: true so anyone can call it. It forwards GET /quote to Quotes and relays the response.

The binding

const quotes = yield* GCP.Run.InvokeService(Quotes);
const response = yield* quotes.fetch("/quote");

Provide GCP.Run.InvokeServiceHttp on the caller.

When What InvokeService does
Deploy Grants roles/run.invoker on the Quotes service's own IAM policy to the Gateway's runtime service account (no project-level role). Binds the Quotes URL into the Gateway.
Runtime Mints an ID token from the metadata server (.../service-accounts/default/identity?audience=<Quotes URL>), caches it until 5 minutes before it expires, and sends it as Authorization: Bearer.

Alchemy mints each host's runtime service account, so the grant applies to the Gateway only. Other services in the project still get 403.

Deploy

Credentials come from your alchemy profile: run alchemy profile once and pick GCP (Service account JSON for a key file, or Stored for an access token or key kept in ~/.alchemy/credentials, plus a default region), then deploy with --profile <name>.

pnpm deploy --profile <name>

This needs Docker (Alchemy builds both images locally). The stack outputs url (the gateway) and quotesUrl:

curl "$url/quote?n=1"       # 200 — served by Quotes via the gateway
curl "$quotesUrl/quote"     # 403 — no Google identity

To call Quotes as yourself, send your own identity token. This works only if your account holds run.invoker on it (project owners do):

curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$quotesUrl/quote"

Test

ALCHEMY_PROFILE=<name> bun test

The test deploys the stack and checks three things: the gateway returns 200 with a body from Quotes, a direct unauthenticated call to Quotes returns 403, and run.invoker on Quotes is held by the gateway's service account only. It then destroys the stack and confirms both services are gone. The test is skipped when Docker is not running.

Destroy

pnpm destroy --profile <name>